bnet-authenticator 0.1.6 → 0.1.8
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 +7 -0
- data/README.md +40 -28
- data/bnet-authenticator.gemspec +5 -4
- data/lib/bnet/attributes/restorecode.rb +34 -0
- data/lib/bnet/attributes/secret.rb +25 -0
- data/lib/bnet/attributes/serial.rb +23 -0
- data/lib/bnet/authenticator.rb +37 -37
- data/lib/bnet/authenticator/core.rb +50 -0
- data/lib/bnet/authenticator/version.rb +1 -1
- data/lib/bnet/commands/token.rb +7 -7
- data/lib/bnet/constants.rb +19 -16
- data/test/test_battlenet_authenticator.rb +16 -4
- metadata +42 -37
- data/lib/bnet/authenticator_helper.rb +0 -95
- data/lib/bnet/support.rb +0 -63
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 0d94b65670bd3f1a4459dd364e9849c926ac646d
|
4
|
+
data.tar.gz: 78084b93e3e2a87566e2a3526211f2e5680fd1bc
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 92a2c1dd5afe2bb2b3affdf3746c7221f19e549988aa03dcd3908053d0fe90e0d444d49e4d653838ba728d849c1f09094c70e6c9f2beb151797f09efd273aeb7
|
7
|
+
data.tar.gz: f29318c63c651cc07f600c4e56233248480d7e195e059dcbc65dc9be02bb302c7c63d6c751ae2fe97508fed07bbb74ab273f1dad38767899ee1ebebcea88f4a7
|
data/README.md
CHANGED
@@ -1,39 +1,51 @@
|
|
1
|
-
Bnet::Authenticator
|
2
|
-
====
|
1
|
+
# Bnet::Authenticator
|
3
2
|
Ruby implementation of the Battle.net Mobile Authenticator.
|
4
3
|
|
5
|
-
[](http://badge.fury.io/rb/bnet-authenticator)
|
5
|
+
[](https://gemnasium.com/dorentus/bnet-authenticator)
|
6
6
|
|
7
|
-
[](https://travis-ci.org/dorentus/bnet-authenticator)
|
8
8
|
|
9
|
-
|
10
|
-
====
|
11
|
-
$ [sudo] gem install bnet-authenticator
|
9
|
+
[](https://coveralls.io/r/dorentus/bnet-authenticator)
|
12
10
|
|
13
|
-
|
14
|
-
|
15
|
-
>> require 'bnet/authenticator'
|
11
|
+
[](https://codeclimate.com/github/dorentus/bnet-authenticator)
|
12
|
+
[](https://codeclimate.com/github/dorentus/bnet-authenticator)
|
16
13
|
|
17
|
-
|
18
|
-
----
|
19
|
-
>> authenticator = Bnet::Authenticator.request_authenticator(:US)
|
20
|
-
=> #<Bnet::Authenticator:0x007f83599ae848 @serial="US-1403-1677-5336", @secret="33a107e6a2927a2aa1be99cfe7b2d08c092a7a2a", @region=:US, @restorecode="4YV9XZVNMX">
|
14
|
+
<sub>FYI: Badge images provided by http://shields.io/</sub>
|
21
15
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
16
|
+
## Installation
|
17
|
+
```bash
|
18
|
+
$ [sudo] gem install bnet-authenticator
|
19
|
+
```
|
26
20
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
21
|
+
## Using the library
|
22
|
+
```irb
|
23
|
+
>> require 'bnet/authenticator'
|
24
|
+
```
|
31
25
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
26
|
+
### Request a new authenticator
|
27
|
+
```irb
|
28
|
+
>> authenticator = Bnet::Authenticator.request_authenticator(:US)
|
29
|
+
=> {:serial=>"US-1405-0242-3258", :secret=>"778275450e5c3e092bc4fe901cd7c11241166c88", :restorecode=>"0WCRH9Z926", :region=>:US}
|
30
|
+
```
|
36
31
|
|
37
|
-
|
38
|
-
|
32
|
+
### Get a token
|
33
|
+
```irb
|
34
|
+
>> authenticator.get_token
|
35
|
+
=> ["38530888", 1399038930]
|
36
|
+
```
|
37
|
+
|
38
|
+
### Restore an authenticator from server
|
39
|
+
```irb
|
40
|
+
>> Bnet::Authenticator.restore_authenticator('CN-1402-1943-1283', '4CKBN08QEB')
|
41
|
+
=> {:serial=>"CN-1402-1943-1283", :secret=>"4202aa2182640745d8a807e0fe7e34b30c1edb23", :restorecode=>"4CKBN08QEB", :region=>:CN}
|
42
|
+
```
|
43
|
+
|
44
|
+
### Initialize an authenticator with given serial and secret
|
45
|
+
```irb
|
46
|
+
>> Bnet::Authenticator.new('CN-1402-1943-1283', '4202aa2182640745d8a807e0fe7e34b30c1edb23')
|
47
|
+
=> {:serial=>"CN-1402-1943-1283", :secret=>"4202aa2182640745d8a807e0fe7e34b30c1edb23", :restorecode=>"4CKBN08QEB", :region=>:CN}
|
48
|
+
```
|
49
|
+
|
50
|
+
## Using the command-line tool
|
39
51
|
Run `bna` and follow instructions.
|
data/bnet-authenticator.gemspec
CHANGED
@@ -17,10 +17,11 @@ 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 'minitest'
|
22
|
-
s.add_development_dependency 'yard'
|
23
|
-
s.add_development_dependency 'coveralls'
|
20
|
+
s.add_development_dependency 'rake'
|
21
|
+
s.add_development_dependency 'minitest'
|
22
|
+
s.add_development_dependency 'yard'
|
23
|
+
s.add_development_dependency 'coveralls'
|
24
|
+
s.add_development_dependency 'codeclimate-test-reporter'
|
24
25
|
end
|
25
26
|
|
26
27
|
s.files = `git ls-files`.split("\n") - %w(.travis.yml .gitignore)
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'digest/sha1'
|
2
|
+
|
3
|
+
module Bnet
|
4
|
+
module Attributes
|
5
|
+
class Restorecode
|
6
|
+
attr_reader :text
|
7
|
+
|
8
|
+
def initialize(serial_or_restorecode, secret = nil)
|
9
|
+
if secret.nil?
|
10
|
+
restorecode = serial_or_restorecode
|
11
|
+
raise BadInputError.new("bad restoration code #{restorecode}") unless restorecode =~ /[0-9A-Z]{10}/
|
12
|
+
@text = restorecode
|
13
|
+
else
|
14
|
+
serial = serial_or_restorecode.is_a?(Serial) ? serial_or_restorecode : Serial.new(serial_or_restorecode)
|
15
|
+
secret = Secret.new secret unless secret.is_a?(Secret)
|
16
|
+
|
17
|
+
bin = Digest::SHA1.digest(serial.normalized + secret.binary)
|
18
|
+
|
19
|
+
@text = bin.split(//).last(10).join.bytes.map do |v|
|
20
|
+
RESTORECODE_MAP[v & 0x1f]
|
21
|
+
end.pack('C*')
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def binary
|
26
|
+
text.bytes.map do |c|
|
27
|
+
RESTORECODE_MAP_INVERSE[c]
|
28
|
+
end.pack('C*')
|
29
|
+
end
|
30
|
+
|
31
|
+
alias_method :to_s, :text
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Bnet
|
2
|
+
module Attributes
|
3
|
+
class Secret
|
4
|
+
attr_reader :text
|
5
|
+
|
6
|
+
def initialize(secret)
|
7
|
+
secret = secret.to_s
|
8
|
+
|
9
|
+
if secret =~ /[0-9a-f]{40}/i
|
10
|
+
@text = secret
|
11
|
+
elsif secret.length == 20 # treated as binary input
|
12
|
+
@text = secret.unpack('C*').map{ |i| i.to_s(16).rjust(2, '0') }.join
|
13
|
+
else
|
14
|
+
raise BadInputError.new("bad secret #{secret}")
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def binary
|
19
|
+
text.scan(/.{2}/).map{|s| s.to_i(16)}.pack('C*')
|
20
|
+
end
|
21
|
+
|
22
|
+
alias_method :to_s, :text
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Bnet
|
2
|
+
module Attributes
|
3
|
+
class Serial
|
4
|
+
attr_reader :normalized
|
5
|
+
|
6
|
+
def initialize(serial)
|
7
|
+
serial = serial.to_s.upcase.gsub(/-/, '')
|
8
|
+
raise BadInputError.new("bad serial #{serial}") unless serial =~ Regexp.new("^(#{AUTHENTICATOR_HOSTS.keys.join('|')})\\d{12}$")
|
9
|
+
@normalized = serial
|
10
|
+
end
|
11
|
+
|
12
|
+
def prettified
|
13
|
+
"#{normalized[0, 2]}-" + normalized[2, 12].scan(/.{4}/).join('-')
|
14
|
+
end
|
15
|
+
|
16
|
+
def region
|
17
|
+
normalized[0, 2].upcase.to_sym
|
18
|
+
end
|
19
|
+
|
20
|
+
alias_method :to_s, :prettified
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/bnet/authenticator.rb
CHANGED
@@ -1,52 +1,51 @@
|
|
1
1
|
require 'bnet/errors'
|
2
|
-
require 'bnet/
|
2
|
+
require 'bnet/constants'
|
3
|
+
|
4
|
+
require 'bnet/attributes/serial'
|
5
|
+
require 'bnet/attributes/secret'
|
6
|
+
require 'bnet/attributes/restorecode'
|
7
|
+
|
8
|
+
require 'bnet/authenticator/core'
|
3
9
|
|
4
10
|
module Bnet
|
5
11
|
|
6
12
|
# The Battle.net authenticator
|
7
13
|
class Authenticator
|
8
|
-
include AuthenticatorHelper
|
9
14
|
|
10
15
|
# @!attribute [r] serial
|
11
16
|
# @return [String] serial
|
12
|
-
|
13
|
-
self.class.prettify_serial(@normalized_serial)
|
14
|
-
end
|
17
|
+
attr_reader :serial
|
15
18
|
|
16
19
|
# @!attribute [r] secret
|
17
|
-
# @return [String]
|
20
|
+
# @return [String] hexlified secret
|
18
21
|
attr_reader :secret
|
19
22
|
|
20
23
|
# @!attribute [r] restorecode
|
21
24
|
# @return [String] restoration code
|
22
|
-
|
23
|
-
restorecode_bin = Digest::SHA1.digest(@normalized_serial + secret.as_hex_to_bin)
|
24
|
-
self.class.encode_restorecode(restorecode_bin.split(//).last(10).join)
|
25
|
-
end
|
25
|
+
attr_reader :restorecode
|
26
26
|
|
27
27
|
# @!attribute [r] region
|
28
28
|
# @return [Symbol] region
|
29
|
-
|
30
|
-
self.class.extract_region(@normalized_serial)
|
31
|
-
end
|
29
|
+
attr_reader :region
|
32
30
|
|
33
31
|
# Create a new authenticator with given serial and secret
|
34
32
|
# @param serial [String]
|
35
33
|
# @param secret [String]
|
36
34
|
def initialize(serial, secret)
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
@
|
35
|
+
serial = Bnet::Attributes::Serial.new serial
|
36
|
+
secret = Bnet::Attributes::Secret.new secret
|
37
|
+
restorecode = Bnet::Attributes::Restorecode.new serial, secret
|
38
|
+
|
39
|
+
@serial = serial.to_s
|
40
|
+
@secret = secret.to_s
|
41
|
+
@restorecode = restorecode.to_s
|
42
|
+
@region = serial.region
|
42
43
|
end
|
43
44
|
|
44
45
|
# Request a new authenticator from server
|
45
46
|
# @param region [Symbol]
|
46
47
|
# @return [Bnet::Authenticator]
|
47
48
|
def self.request_authenticator(region)
|
48
|
-
raise BadInputError.new("bad region #{region}") unless is_valid_region?(region)
|
49
|
-
|
50
49
|
k = create_one_time_pad(37)
|
51
50
|
|
52
51
|
payload_plain = "\1" + k + region.to_s + CLIENT_MODEL.ljust(16, "\0")[0, 16]
|
@@ -56,7 +55,7 @@ module Bnet
|
|
56
55
|
|
57
56
|
decrypted = decrypt_response(response_body[8, 37], k)
|
58
57
|
|
59
|
-
Authenticator.new(decrypted[20, 17], decrypted[0, 20]
|
58
|
+
Authenticator.new(decrypted[20, 17], decrypted[0, 20])
|
60
59
|
end
|
61
60
|
|
62
61
|
# Restore an authenticator from server
|
@@ -64,35 +63,36 @@ module Bnet
|
|
64
63
|
# @param restorecode [String]
|
65
64
|
# @return [Bnet::Authenticator]
|
66
65
|
def self.restore_authenticator(serial, restorecode)
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
normalized_serial = normalize_serial(serial)
|
71
|
-
region = extract_region(normalized_serial)
|
66
|
+
serial = Bnet::Attributes::Serial.new serial
|
67
|
+
restorecode = Bnet::Attributes::Restorecode.new restorecode
|
72
68
|
|
73
69
|
# stage 1
|
74
|
-
challenge = request_for('restore (stage 1)',
|
70
|
+
challenge = request_for('restore (stage 1)',
|
71
|
+
serial.region,
|
72
|
+
RESTORE_INIT_REQUEST_PATH,
|
73
|
+
serial.normalized)
|
75
74
|
|
76
75
|
# stage 2
|
77
76
|
key = create_one_time_pad(20)
|
78
77
|
|
79
|
-
digest = Digest::HMAC.digest(
|
80
|
-
|
78
|
+
digest = Digest::HMAC.digest(serial.normalized + challenge,
|
79
|
+
restorecode.binary,
|
81
80
|
Digest::SHA1)
|
82
81
|
|
83
|
-
payload =
|
82
|
+
payload = serial.normalized + rsa_encrypt_bin(digest + key)
|
84
83
|
|
85
|
-
response_body = request_for('restore (stage 2)',
|
84
|
+
response_body = request_for('restore (stage 2)',
|
85
|
+
serial.region,
|
86
|
+
RESTORE_VALIDATE_REQUEST_PATH,
|
87
|
+
payload)
|
86
88
|
|
87
|
-
Authenticator.new(
|
89
|
+
Authenticator.new(serial, decrypt_response(response_body, key))
|
88
90
|
end
|
89
91
|
|
90
92
|
# Get server's time
|
91
93
|
# @param region [Symbol]
|
92
94
|
# @return [Integer] server timestamp in seconds
|
93
95
|
def self.request_server_time(region)
|
94
|
-
raise BadInputError.new("bad region #{region}") unless is_valid_region?(region)
|
95
|
-
|
96
96
|
server_time_big_endian = request_for('server time', region, TIME_REQUEST_PATH)
|
97
97
|
server_time_big_endian.unpack('Q>')[0].to_f / 1000
|
98
98
|
end
|
@@ -103,10 +103,10 @@ module Bnet
|
|
103
103
|
# defaults to current time
|
104
104
|
# @return [String, Integer] token and the next timestamp token to change
|
105
105
|
def self.get_token(secret, timestamp = nil)
|
106
|
-
|
106
|
+
secret = Bnet::Attributes::Secret.new secret
|
107
107
|
|
108
108
|
current = (timestamp || Time.now.getutc.to_i) / 30
|
109
|
-
digest = Digest::HMAC.digest([current].pack('Q>'), secret.
|
109
|
+
digest = Digest::HMAC.digest([current].pack('Q>'), secret.binary, Digest::SHA1)
|
110
110
|
start_position = digest[19].ord & 0xf
|
111
111
|
|
112
112
|
token = digest[start_position, 4].unpack('L>')[0] & 0x7fffffff
|
@@ -119,7 +119,7 @@ module Bnet
|
|
119
119
|
# defaults to current time
|
120
120
|
# @return [String, Integer] token and the next timestamp token to change
|
121
121
|
def get_token(timestamp = nil)
|
122
|
-
self.class.get_token(
|
122
|
+
self.class.get_token(secret, timestamp)
|
123
123
|
end
|
124
124
|
|
125
125
|
# Hash representation of this authenticator
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'digest/sha1'
|
3
|
+
|
4
|
+
module Bnet
|
5
|
+
|
6
|
+
class Authenticator
|
7
|
+
|
8
|
+
class << self
|
9
|
+
|
10
|
+
def create_one_time_pad(length)
|
11
|
+
(0..1.0/0.0).reduce('') do |memo, i|
|
12
|
+
break memo if memo.length >= length
|
13
|
+
memo << Digest::SHA1.hexdigest(rand().to_s)
|
14
|
+
end[0, length]
|
15
|
+
end
|
16
|
+
|
17
|
+
def decrypt_response(text, key)
|
18
|
+
text.bytes.zip(key.bytes).reduce('') do |memo, pair|
|
19
|
+
memo + (pair[0] ^ pair[1]).chr
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def rsa_encrypt_bin(bin)
|
24
|
+
i = bin.unpack('C*').map{ |i| i.to_s(16).rjust(2, '0') }.join.to_i(16)
|
25
|
+
(i ** RSA_KEY % RSA_MOD).to_s(16).scan(/.{2}/).map {|s| s.to_i(16)}.pack('C*')
|
26
|
+
end
|
27
|
+
|
28
|
+
def request_for(label, region, path, body = nil)
|
29
|
+
raise BadInputError.new("bad region #{region}") unless AUTHENTICATOR_HOSTS.has_key? region
|
30
|
+
|
31
|
+
request = body.nil? ? Net::HTTP::Get.new(path) : Net::HTTP::Post.new(path)
|
32
|
+
request.content_type = 'application/octet-stream'
|
33
|
+
request.body = body unless body.nil?
|
34
|
+
|
35
|
+
response = Net::HTTP.new(AUTHENTICATOR_HOSTS[region]).start do |http|
|
36
|
+
http.request request
|
37
|
+
end
|
38
|
+
|
39
|
+
if response.code.to_i != 200
|
40
|
+
raise RequestFailedError.new("Error requesting #{label}: #{response.code}")
|
41
|
+
end
|
42
|
+
|
43
|
+
response.body
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
data/lib/bnet/commands/token.rb
CHANGED
@@ -26,7 +26,6 @@ module Bnet
|
|
26
26
|
|
27
27
|
token, next_timestamp = Authenticator.get_token(secret)
|
28
28
|
|
29
|
-
puts token
|
30
29
|
if @options.repeat
|
31
30
|
interrupted = false
|
32
31
|
trap("INT") { interrupted = true } # traps Ctrl-C
|
@@ -35,21 +34,22 @@ module Bnet
|
|
35
34
|
sleep 1
|
36
35
|
|
37
36
|
if Time.now.getutc.to_i < next_timestamp
|
38
|
-
|
37
|
+
seconds = next_timestamp - Time.now.getutc.to_i
|
38
|
+
h, c = color_of(seconds)
|
39
|
+
puts "\e[s\e[%d;%dm\e[5m%02d\e[25m\t->\t%s\t<-\e[1A\e[0m\e[u" % [h, c, seconds, token]
|
39
40
|
next
|
40
41
|
end
|
41
42
|
|
42
43
|
token, next_timestamp = Authenticator.get_token(secret)
|
43
|
-
puts token
|
44
44
|
end
|
45
|
+
else
|
46
|
+
puts token
|
45
47
|
end
|
46
48
|
end
|
47
49
|
|
48
50
|
private
|
49
51
|
|
50
|
-
def
|
51
|
-
return unless output.tty?
|
52
|
-
|
52
|
+
def color_of(seconds)
|
53
53
|
case
|
54
54
|
when seconds > 25 then h, c = 1, 32
|
55
55
|
when seconds > 20 then h, c = 0, 32
|
@@ -60,7 +60,7 @@ module Bnet
|
|
60
60
|
h, c = 1, 31
|
61
61
|
end
|
62
62
|
|
63
|
-
|
63
|
+
[h, c]
|
64
64
|
end
|
65
65
|
|
66
66
|
end
|
data/lib/bnet/constants.rb
CHANGED
@@ -1,31 +1,34 @@
|
|
1
1
|
module Bnet
|
2
2
|
|
3
3
|
CLIENT_MODEL = 'bn/authenticator'
|
4
|
-
RSA_MOD =
|
4
|
+
RSA_MOD = "955e4bd989f3917d2f15544a7e0504eb\
|
5
|
+
9d7bb66b6f8a2fe470e453c779200e5e\
|
6
|
+
3ad2e43a02d06c4adbd8d328f1a426b8\
|
7
|
+
3658e88bfd949b2af4eaf30054673a14\
|
8
|
+
19a250fa4cc1278d12855b5b25818d16\
|
9
|
+
2c6e6ee2ab4a350d401d78f6ddb99711\
|
10
|
+
e72626b48bd8b5b0b7f3acf9ea3c9e00\
|
11
|
+
05fee59e19136cdb7c83f2ab8b0a2a99".gsub(/\s+/, '').to_i(16)
|
5
12
|
RSA_KEY = 257
|
6
13
|
AUTHENTICATOR_HOSTS = {
|
7
14
|
:CN => "mobile-service.battlenet.com.cn",
|
8
|
-
:EU => "
|
9
|
-
:US => "
|
15
|
+
:EU => "mobile-service.blizzard.com",
|
16
|
+
:US => "mobile-service.blizzard.com",
|
10
17
|
}
|
11
18
|
ENROLLMENT_REQUEST_PATH = '/enrollment/enroll.htm'
|
12
19
|
TIME_REQUEST_PATH = '/enrollment/time.htm'
|
13
20
|
RESTORE_INIT_REQUEST_PATH = '/enrollment/initiatePaperRestore.htm'
|
14
21
|
RESTORE_VALIDATE_REQUEST_PATH = '/enrollment/validatePaperRestore.htm'
|
15
22
|
|
16
|
-
RESTORECODE_MAP =
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
c
|
26
|
-
end
|
27
|
-
memo
|
28
|
-
end
|
23
|
+
RESTORECODE_MAP = {
|
24
|
+
0=>48, 1=>49, 2=>50, 3=>51, 4=>52,
|
25
|
+
5=>53, 6=>54, 7=>55, 8=>56, 9=>57,
|
26
|
+
10=>65, 11=>66, 12=>67, 13=>68, 14=>69,
|
27
|
+
15=>70, 16=>71, 17=>72, 18=>74, 19=>75,
|
28
|
+
20=>77, 21=>78, 22=>80, 23=>81, 24=>82,
|
29
|
+
25=>84, 26=>85, 27=>86, 28=>87, 29=>88,
|
30
|
+
30=>89, 31=>90, 32=>91
|
31
|
+
}
|
29
32
|
RESTORECODE_MAP_INVERSE = RESTORECODE_MAP.invert
|
30
33
|
|
31
34
|
end
|
@@ -1,6 +1,9 @@
|
|
1
1
|
require 'coveralls'
|
2
2
|
Coveralls.wear_merged!
|
3
3
|
|
4
|
+
require 'codeclimate-test-reporter'
|
5
|
+
CodeClimate::TestReporter.start
|
6
|
+
|
4
7
|
gem "minitest"
|
5
8
|
require 'minitest/autorun'
|
6
9
|
require 'bnet/authenticator'
|
@@ -18,21 +21,25 @@ class Bnet::AuthenticatorTest < Minitest::Test
|
|
18
21
|
|
19
22
|
def test_argument_error
|
20
23
|
assert_raises ::Bnet::BadInputError do
|
21
|
-
Bnet::Authenticator.new
|
24
|
+
Bnet::Authenticator.new 'ABC', ''
|
25
|
+
end
|
26
|
+
|
27
|
+
assert_raises ::Bnet::BadInputError do
|
28
|
+
Bnet::Authenticator.new DEFAULT_SERIAL, 'ASDF'
|
22
29
|
end
|
23
30
|
|
24
31
|
assert_raises ::Bnet::BadInputError do
|
25
|
-
Bnet::Authenticator.request_authenticator
|
32
|
+
Bnet::Authenticator.request_authenticator 'SG'
|
26
33
|
end
|
27
34
|
|
28
35
|
assert_raises ::Bnet::BadInputError do
|
29
|
-
Bnet::Authenticator.restore_authenticator
|
36
|
+
Bnet::Authenticator.restore_authenticator 'DDDD', 'EEE'
|
30
37
|
end
|
31
38
|
end
|
32
39
|
|
33
40
|
def test_request_new_serial
|
34
41
|
begin
|
35
|
-
authenticator = Bnet::Authenticator.request_authenticator
|
42
|
+
authenticator = Bnet::Authenticator.request_authenticator :US
|
36
43
|
assert_equal :US, authenticator.region
|
37
44
|
refute_nil authenticator.serial
|
38
45
|
refute_nil authenticator.secret
|
@@ -59,6 +66,10 @@ class Bnet::AuthenticatorTest < Minitest::Test
|
|
59
66
|
end
|
60
67
|
end
|
61
68
|
|
69
|
+
def test_bin
|
70
|
+
assert_equal true, system("ruby -Ilib bin/bna", :out => "/dev/null")
|
71
|
+
end
|
72
|
+
|
62
73
|
private
|
63
74
|
|
64
75
|
def is_default_authenticator(authenticator)
|
@@ -69,5 +80,6 @@ class Bnet::AuthenticatorTest < Minitest::Test
|
|
69
80
|
assert_equal ['61459300', 1347279360], authenticator.get_token(1347279358)
|
70
81
|
assert_equal ['61459300', 1347279360], authenticator.get_token(1347279359)
|
71
82
|
assert_equal ['75939986', 1347279390], authenticator.get_token(1347279360)
|
83
|
+
assert_equal ['59914793', 1370448030], authenticator.get_token(1370448000)
|
72
84
|
end
|
73
85
|
end
|
metadata
CHANGED
@@ -1,80 +1,85 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bnet-authenticator
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
5
|
-
prerelease:
|
4
|
+
version: 0.1.8
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- ZHANG Yi
|
9
8
|
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date:
|
11
|
+
date: 2017-04-18 00:00:00.000000000 Z
|
13
12
|
dependencies:
|
14
13
|
- !ruby/object:Gem::Dependency
|
15
14
|
name: rake
|
16
15
|
requirement: !ruby/object:Gem::Requirement
|
17
|
-
none: false
|
18
16
|
requirements:
|
19
|
-
- -
|
17
|
+
- - ">="
|
20
18
|
- !ruby/object:Gem::Version
|
21
|
-
version: '
|
19
|
+
version: '0'
|
22
20
|
type: :development
|
23
21
|
prerelease: false
|
24
22
|
version_requirements: !ruby/object:Gem::Requirement
|
25
|
-
none: false
|
26
23
|
requirements:
|
27
|
-
- -
|
24
|
+
- - ">="
|
28
25
|
- !ruby/object:Gem::Version
|
29
|
-
version: '
|
26
|
+
version: '0'
|
30
27
|
- !ruby/object:Gem::Dependency
|
31
28
|
name: minitest
|
32
29
|
requirement: !ruby/object:Gem::Requirement
|
33
|
-
none: false
|
34
30
|
requirements:
|
35
|
-
- -
|
31
|
+
- - ">="
|
36
32
|
- !ruby/object:Gem::Version
|
37
|
-
version: '
|
33
|
+
version: '0'
|
38
34
|
type: :development
|
39
35
|
prerelease: false
|
40
36
|
version_requirements: !ruby/object:Gem::Requirement
|
41
|
-
none: false
|
42
37
|
requirements:
|
43
|
-
- -
|
38
|
+
- - ">="
|
44
39
|
- !ruby/object:Gem::Version
|
45
|
-
version: '
|
40
|
+
version: '0'
|
46
41
|
- !ruby/object:Gem::Dependency
|
47
42
|
name: yard
|
48
43
|
requirement: !ruby/object:Gem::Requirement
|
49
|
-
none: false
|
50
44
|
requirements:
|
51
|
-
- -
|
45
|
+
- - ">="
|
52
46
|
- !ruby/object:Gem::Version
|
53
|
-
version: '0
|
47
|
+
version: '0'
|
54
48
|
type: :development
|
55
49
|
prerelease: false
|
56
50
|
version_requirements: !ruby/object:Gem::Requirement
|
57
|
-
none: false
|
58
51
|
requirements:
|
59
|
-
- -
|
52
|
+
- - ">="
|
60
53
|
- !ruby/object:Gem::Version
|
61
|
-
version: '0
|
54
|
+
version: '0'
|
62
55
|
- !ruby/object:Gem::Dependency
|
63
56
|
name: coveralls
|
64
57
|
requirement: !ruby/object:Gem::Requirement
|
65
|
-
none: false
|
66
58
|
requirements:
|
67
|
-
- -
|
59
|
+
- - ">="
|
68
60
|
- !ruby/object:Gem::Version
|
69
|
-
version: '0
|
61
|
+
version: '0'
|
70
62
|
type: :development
|
71
63
|
prerelease: false
|
72
64
|
version_requirements: !ruby/object:Gem::Requirement
|
73
|
-
none: false
|
74
65
|
requirements:
|
75
|
-
- -
|
66
|
+
- - ">="
|
76
67
|
- !ruby/object:Gem::Version
|
77
|
-
version: '0
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: codeclimate-test-reporter
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
78
83
|
description: Ruby implementation of the Battle.net Mobile Authenticator
|
79
84
|
email:
|
80
85
|
- zhangyi.cn@gmail.com
|
@@ -83,16 +88,19 @@ executables:
|
|
83
88
|
extensions: []
|
84
89
|
extra_rdoc_files: []
|
85
90
|
files:
|
86
|
-
- .yardopts
|
91
|
+
- ".yardopts"
|
87
92
|
- COPYING
|
88
93
|
- Gemfile
|
89
94
|
- README.md
|
90
95
|
- Rakefile
|
91
96
|
- bin/bna
|
92
97
|
- bnet-authenticator.gemspec
|
98
|
+
- lib/bnet/attributes/restorecode.rb
|
99
|
+
- lib/bnet/attributes/secret.rb
|
100
|
+
- lib/bnet/attributes/serial.rb
|
93
101
|
- lib/bnet/authenticator.rb
|
102
|
+
- lib/bnet/authenticator/core.rb
|
94
103
|
- lib/bnet/authenticator/version.rb
|
95
|
-
- lib/bnet/authenticator_helper.rb
|
96
104
|
- lib/bnet/command.rb
|
97
105
|
- lib/bnet/commands/help.rb
|
98
106
|
- lib/bnet/commands/info.rb
|
@@ -101,33 +109,30 @@ files:
|
|
101
109
|
- lib/bnet/commands/token.rb
|
102
110
|
- lib/bnet/constants.rb
|
103
111
|
- lib/bnet/errors.rb
|
104
|
-
- lib/bnet/support.rb
|
105
112
|
- test/test_battlenet_authenticator.rb
|
106
113
|
homepage: https://github.com/dorentus/bnet-authenticator
|
107
114
|
licenses:
|
108
115
|
- MIT
|
116
|
+
metadata: {}
|
109
117
|
post_install_message:
|
110
118
|
rdoc_options: []
|
111
119
|
require_paths:
|
112
120
|
- lib
|
113
121
|
required_ruby_version: !ruby/object:Gem::Requirement
|
114
|
-
none: false
|
115
122
|
requirements:
|
116
|
-
- -
|
123
|
+
- - ">="
|
117
124
|
- !ruby/object:Gem::Version
|
118
125
|
version: 1.9.3
|
119
126
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
120
|
-
none: false
|
121
127
|
requirements:
|
122
|
-
- -
|
128
|
+
- - ">="
|
123
129
|
- !ruby/object:Gem::Version
|
124
130
|
version: '0'
|
125
131
|
requirements: []
|
126
132
|
rubyforge_project:
|
127
|
-
rubygems_version:
|
133
|
+
rubygems_version: 2.6.7
|
128
134
|
signing_key:
|
129
|
-
specification_version:
|
135
|
+
specification_version: 4
|
130
136
|
summary: Battle.net Mobile Authenticator
|
131
137
|
test_files:
|
132
138
|
- test/test_battlenet_authenticator.rb
|
133
|
-
has_rdoc:
|
@@ -1,95 +0,0 @@
|
|
1
|
-
require 'bnet/support'
|
2
|
-
require 'bnet/constants'
|
3
|
-
require 'digest/sha1'
|
4
|
-
require 'digest/hmac'
|
5
|
-
require 'net/http'
|
6
|
-
|
7
|
-
module Bnet
|
8
|
-
|
9
|
-
module AuthenticatorHelper
|
10
|
-
|
11
|
-
def self.included(base)
|
12
|
-
|
13
|
-
class << base
|
14
|
-
|
15
|
-
def is_valid_serial?(serial)
|
16
|
-
normalized_serial = normalize_serial(serial)
|
17
|
-
normalized_serial =~ Regexp.new("^(#{AUTHENTICATOR_HOSTS.keys.join('|')})\\d{12}$") && is_valid_region?(extract_region(normalized_serial))
|
18
|
-
end
|
19
|
-
|
20
|
-
def normalize_serial(serial)
|
21
|
-
serial.upcase.gsub(/-/, '')
|
22
|
-
end
|
23
|
-
|
24
|
-
def extract_region(serial)
|
25
|
-
serial[0, 2].upcase.to_sym
|
26
|
-
end
|
27
|
-
|
28
|
-
def prettify_serial(serial)
|
29
|
-
"#{serial[0, 2]}-" + serial[2, 12].scan(/.{4}/).join('-')
|
30
|
-
end
|
31
|
-
|
32
|
-
def is_valid_secret?(secret)
|
33
|
-
secret =~ /[0-9a-f]{40}/i
|
34
|
-
end
|
35
|
-
|
36
|
-
def is_valid_region?(region)
|
37
|
-
AUTHENTICATOR_HOSTS.has_key? region
|
38
|
-
end
|
39
|
-
|
40
|
-
def is_valid_restorecode?(restorecode)
|
41
|
-
restorecode =~ /[0-9A-Z]{10}/
|
42
|
-
end
|
43
|
-
|
44
|
-
def encode_restorecode(bin)
|
45
|
-
bin.bytes.map do |v|
|
46
|
-
RESTORECODE_MAP[v & 0x1f]
|
47
|
-
end.as_bytes_to_bin
|
48
|
-
end
|
49
|
-
|
50
|
-
def decode_restorecode(str)
|
51
|
-
str.bytes.map do |c|
|
52
|
-
RESTORECODE_MAP_INVERSE[c]
|
53
|
-
end.as_bytes_to_bin
|
54
|
-
end
|
55
|
-
|
56
|
-
def create_one_time_pad(length)
|
57
|
-
(0..1.0/0.0).reduce('') do |memo, i|
|
58
|
-
break memo if memo.length >= length
|
59
|
-
memo << Digest::SHA1.hexdigest(rand().to_s)
|
60
|
-
end[0, length]
|
61
|
-
end
|
62
|
-
|
63
|
-
def decrypt_response(text, key)
|
64
|
-
text.bytes.zip(key.bytes).reduce('') do |memo, pair|
|
65
|
-
memo + (pair[0] ^ pair[1]).chr
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
def rsa_encrypt_bin(bin)
|
70
|
-
(bin.as_bin_to_i ** RSA_KEY % RSA_MOD).to_bin
|
71
|
-
end
|
72
|
-
|
73
|
-
def request_for(label, region, path, body = nil)
|
74
|
-
request = body.nil? ? Net::HTTP::Get.new(path) : Net::HTTP::Post.new(path)
|
75
|
-
request.content_type = 'application/octet-stream'
|
76
|
-
request.body = body unless body.nil?
|
77
|
-
|
78
|
-
response = Net::HTTP.new(AUTHENTICATOR_HOSTS[region]).start do |http|
|
79
|
-
http.request request
|
80
|
-
end
|
81
|
-
|
82
|
-
if response.code.to_i != 200
|
83
|
-
raise RequestFailedError.new("Error requesting #{label}: #{response.code}")
|
84
|
-
end
|
85
|
-
|
86
|
-
response.body
|
87
|
-
end
|
88
|
-
|
89
|
-
end
|
90
|
-
|
91
|
-
end
|
92
|
-
|
93
|
-
end
|
94
|
-
|
95
|
-
end
|
data/lib/bnet/support.rb
DELETED
@@ -1,63 +0,0 @@
|
|
1
|
-
module Bnet
|
2
|
-
|
3
|
-
module Support
|
4
|
-
|
5
|
-
module Array
|
6
|
-
|
7
|
-
def as_bytes_to_bin
|
8
|
-
pack('C*')
|
9
|
-
end
|
10
|
-
|
11
|
-
def as_bytes_to_hex
|
12
|
-
'%02x' * length % self
|
13
|
-
end
|
14
|
-
|
15
|
-
def as_bytes_to_i
|
16
|
-
as_bytes_to_hex.to_i(16)
|
17
|
-
end
|
18
|
-
|
19
|
-
end
|
20
|
-
|
21
|
-
module Integer
|
22
|
-
|
23
|
-
def to_bytes
|
24
|
-
to_s(16).scan(/.{2}/).map {|s| s.to_i(16)}
|
25
|
-
end
|
26
|
-
|
27
|
-
def to_bin
|
28
|
-
to_bytes.as_bytes_to_bin
|
29
|
-
end
|
30
|
-
|
31
|
-
end
|
32
|
-
|
33
|
-
module String
|
34
|
-
|
35
|
-
def as_bin_to_i
|
36
|
-
bytes.to_a.as_bytes_to_i
|
37
|
-
end
|
38
|
-
|
39
|
-
def as_bin_to_hex
|
40
|
-
bytes.to_a.as_bytes_to_hex
|
41
|
-
end
|
42
|
-
|
43
|
-
def as_hex_to_bin
|
44
|
-
to_i(16).to_bytes.as_bytes_to_bin
|
45
|
-
end
|
46
|
-
|
47
|
-
end
|
48
|
-
|
49
|
-
end
|
50
|
-
|
51
|
-
end
|
52
|
-
|
53
|
-
class Array
|
54
|
-
include Bnet::Support::Array
|
55
|
-
end
|
56
|
-
|
57
|
-
class Integer
|
58
|
-
include Bnet::Support::Integer
|
59
|
-
end
|
60
|
-
|
61
|
-
class String
|
62
|
-
include Bnet::Support::String
|
63
|
-
end
|