Class: Bnet::Authenticator
- Inherits:
-
Object
- Object
- Bnet::Authenticator
- Defined in:
- lib/bnet/authenticator.rb
Overview
The Battle.net authenticator
Instance Attribute Summary collapse
-
#region ⇒ Symbol
readonly
Region.
-
#restorecode ⇒ String
readonly
Restoration code.
-
#secret ⇒ String
readonly
Hexified secret.
-
#serial ⇒ String
readonly
Serial.
Class Method Summary collapse
- .create_one_time_pad(length) ⇒ Object
- .decode_restorecode(str) ⇒ Object
- .decrypt_response(text, key) ⇒ Object
- .encode_restorecode(bin) ⇒ Object
- .extract_region(serial) ⇒ Object
-
.get_token(secret, timestamp = nil) ⇒ String, Integer
Get token from given secret and timestamp.
- .is_valid_region?(region) ⇒ Boolean
- .is_valid_restorecode?(restorecode) ⇒ Boolean
- .is_valid_secret?(secret) ⇒ Boolean
- .is_valid_serial?(serial) ⇒ Boolean
- .normalize_serial(serial) ⇒ Object
- .prettify_serial(serial) ⇒ Object
-
.request_authenticator(region) ⇒ Bnet::Authenticator
Request a new authenticator from server.
- .request_for(label, region, path, body = nil) ⇒ Object
-
.request_server_time(region) ⇒ Integer
Get server’s time.
-
.restore_authenticator(serial, restorecode) ⇒ Bnet::Authenticator
Restore an authenticator from server.
- .rsa_encrypted(integer) ⇒ Object
Instance Method Summary collapse
-
#get_token(timestamp = nil) ⇒ String, Integer
Get authenticator’s token from given timestamp.
-
#initialize(serial, secret) ⇒ Authenticator
constructor
Create a new authenticator with given serial and secret.
-
#to_hash ⇒ Hash
Hash representation of this authenticator.
-
#to_s ⇒ String
String representation of this authenticator.
Constructor Details
#initialize(serial, secret) ⇒ Authenticator
Create a new authenticator with given serial and secret
32 33 34 35 36 37 38 39 40 41 42 43 44 |
# File 'lib/bnet/authenticator.rb', line 32 def initialize(serial, secret) raise BadInputError.new("bad serial #{serial}") unless self.class.is_valid_serial?(serial) raise BadInputError.new("bad secret #{secret}") unless self.class.is_valid_secret?(secret) normalized_serial = self.class.normalize_serial(serial) @serial = self.class.prettify_serial(normalized_serial) @secret = secret @region = self.class.extract_region(normalized_serial) restorecode_bin = Digest::SHA1.digest(normalized_serial + secret.as_hex_to_bin) @restorecode = self.class.encode_restorecode(restorecode_bin.split(//).last(10).join) end |
Instance Attribute Details
#region ⇒ Symbol (readonly)
Returns region.
27 28 29 |
# File 'lib/bnet/authenticator.rb', line 27 def region @region end |
#restorecode ⇒ String (readonly)
Returns restoration code.
23 24 25 |
# File 'lib/bnet/authenticator.rb', line 23 def restorecode @restorecode end |
#secret ⇒ String (readonly)
Returns hexified secret.
19 20 21 |
# File 'lib/bnet/authenticator.rb', line 19 def secret @secret end |
#serial ⇒ String (readonly)
Returns serial.
15 16 17 |
# File 'lib/bnet/authenticator.rb', line 15 def serial @serial end |
Class Method Details
.create_one_time_pad(length) ⇒ Object
182 183 184 185 186 187 |
# File 'lib/bnet/authenticator.rb', line 182 def create_one_time_pad(length) (0..1.0/0.0).reduce('') do |memo, i| break memo if memo.length >= length memo << Digest::SHA1.digest(rand().to_s) end[0, length] end |
.decode_restorecode(str) ⇒ Object
176 177 178 179 180 |
# File 'lib/bnet/authenticator.rb', line 176 def decode_restorecode(str) str.bytes.map do |c| RESTORECODE_MAP_INVERSE[c] end.as_bytes_to_bin end |
.decrypt_response(text, key) ⇒ Object
189 190 191 192 193 |
# File 'lib/bnet/authenticator.rb', line 189 def decrypt_response(text, key) text.bytes.zip(key.bytes).reduce('') do |memo, pair| memo + (pair[0] ^ pair[1]).chr end end |
.encode_restorecode(bin) ⇒ Object
170 171 172 173 174 |
# File 'lib/bnet/authenticator.rb', line 170 def encode_restorecode(bin) bin.bytes.map do |v| RESTORECODE_MAP[v & 0x1f] end.as_bytes_to_bin end |
.extract_region(serial) ⇒ Object
150 151 152 |
# File 'lib/bnet/authenticator.rb', line 150 def extract_region(serial) serial[0, 2].upcase.to_sym end |
.get_token(secret, timestamp = nil) ⇒ String, Integer
Get token from given secret and timestamp
105 106 107 108 109 110 111 112 113 114 |
# File 'lib/bnet/authenticator.rb', line 105 def self.get_token(secret, = nil) raise BadInputError.new("bad seret #{secret}") unless is_valid_secret?(secret) current = ( || Time.now.getutc.to_i) / 30 digest = Digest::HMAC.digest([current].pack('Q>'), secret.as_hex_to_bin, Digest::SHA1) start_position = digest[19].ord & 0xf token = '%08d' % (digest[start_position, 4].as_bin_to_i % 100000000) return token, (current + 1) * 30 end |
.is_valid_region?(region) ⇒ Boolean
162 163 164 |
# File 'lib/bnet/authenticator.rb', line 162 def is_valid_region?(region) AUTHENTICATOR_HOSTS.has_key? region end |
.is_valid_restorecode?(restorecode) ⇒ Boolean
166 167 168 |
# File 'lib/bnet/authenticator.rb', line 166 def is_valid_restorecode?(restorecode) restorecode =~ /[0-9A-Z]{10}/ end |
.is_valid_secret?(secret) ⇒ Boolean
158 159 160 |
# File 'lib/bnet/authenticator.rb', line 158 def is_valid_secret?(secret) secret =~ /[0-9a-f]{40}/i end |
.is_valid_serial?(serial) ⇒ Boolean
141 142 143 144 |
# File 'lib/bnet/authenticator.rb', line 141 def is_valid_serial?(serial) normalized_serial = normalize_serial(serial) normalized_serial =~ Regexp.new("^(#{AUTHENTICATOR_HOSTS.keys.join('|')})\\d{12}$") && is_valid_region?(extract_region(normalized_serial)) end |
.normalize_serial(serial) ⇒ Object
146 147 148 |
# File 'lib/bnet/authenticator.rb', line 146 def normalize_serial(serial) serial.upcase.gsub(/-/, '') end |
.prettify_serial(serial) ⇒ Object
154 155 156 |
# File 'lib/bnet/authenticator.rb', line 154 def prettify_serial(serial) "#{serial[0, 2]}-" + serial[2, 12].scan(/.{4}/).join('-') end |
.request_authenticator(region) ⇒ Bnet::Authenticator
Request a new authenticator from server
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
# File 'lib/bnet/authenticator.rb', line 49 def self.request_authenticator(region) region = region.to_s.upcase.to_sym raise BadInputError.new("bad region #{region}") unless is_valid_region?(region) k = create_one_time_pad(37) payload_plain = "\1" + k + region.to_s + CLIENT_MODEL.ljust(16, "\0")[0, 16] e = rsa_encrypted(payload_plain.as_bin_to_i) response_body = request_for('new serial', region, ENROLLMENT_REQUEST_PATH, e) decrypted = decrypt_response(response_body[8, 37], k) Authenticator.new(decrypted[20, 17], decrypted[0, 20].as_bin_to_hex) end |
.request_for(label, region, path, body = nil) ⇒ Object
199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 |
# File 'lib/bnet/authenticator.rb', line 199 def request_for(label, region, path, body = nil) request = body.nil? ? Net::HTTP::Get.new(path) : Net::HTTP::Post.new(path) request.content_type = 'application/octet-stream' request.body = body unless body.nil? response = Net::HTTP.new(AUTHENTICATOR_HOSTS[region]).start do |http| http.request request end if response.code.to_i != 200 raise RequestFailedError.new("Error requesting #{label}: #{response.code}") end response.body end |
.request_server_time(region) ⇒ Integer
Get server’s time
96 97 98 |
# File 'lib/bnet/authenticator.rb', line 96 def self.request_server_time(region) request_for('server time', region, TIME_REQUEST_PATH).as_bin_to_i.to_f / 1000 end |
.restore_authenticator(serial, restorecode) ⇒ Bnet::Authenticator
Restore an authenticator from server
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
# File 'lib/bnet/authenticator.rb', line 69 def self.restore_authenticator(serial, restorecode) raise BadInputError.new("bad serial #{serial}") unless is_valid_serial?(serial) raise BadInputError.new("bad restoration code #{restorecode}") unless is_valid_restorecode?(restorecode) normalized_serial = normalize_serial(serial) region = extract_region(normalized_serial) # stage 1 challenge = request_for('restore (stage 1)', region, RESTORE_INIT_REQUEST_PATH, normalized_serial) # stage 2 key = create_one_time_pad(20) digest = Digest::HMAC.digest(normalized_serial + challenge, decode_restorecode(restorecode), Digest::SHA1) payload = normalized_serial + rsa_encrypted((digest + key).as_bin_to_i) response_body = request_for('restore (stage 2)', region, RESTORE_VALIDATE_REQUEST_PATH, payload) Authenticator.new(prettify_serial(normalized_serial), decrypt_response(response_body, key).as_bin_to_hex) end |
.rsa_encrypted(integer) ⇒ Object
195 196 197 |
# File 'lib/bnet/authenticator.rb', line 195 def rsa_encrypted(integer) (integer ** RSA_KEY % RSA_MOD).to_bin end |
Instance Method Details
#get_token(timestamp = nil) ⇒ String, Integer
Get authenticator’s token from given timestamp
120 121 122 |
# File 'lib/bnet/authenticator.rb', line 120 def get_token( = nil) self.class.get_token(@secret, ) end |
#to_hash ⇒ Hash
Hash representation of this authenticator
126 127 128 129 130 131 132 |
# File 'lib/bnet/authenticator.rb', line 126 def to_hash { :serial => serial, :secret => secret, :restorecode => restorecode, } end |
#to_s ⇒ String
String representation of this authenticator
136 137 138 |
# File 'lib/bnet/authenticator.rb', line 136 def to_s to_hash.to_s end |