class Bnet::Authenticator
The Battle.net authenticator
Constants
- VERSION
Attributes
@!attribute [r] region @return [Symbol] region
@!attribute [r] restorecode @return [String] restoration code
@!attribute [r] secret @return [String] hexlified secret
@!attribute [r] serial @return [String] serial
Public Class Methods
# File lib/bnet/authenticator/core.rb, line 10 def create_one_time_pad(length) (0..1.0/0.0).reduce('') do |memo, i| break memo if memo.length >= length memo << Digest::SHA1.hexdigest(rand().to_s) end[0, length] end
# File lib/bnet/authenticator/core.rb, line 17 def decrypt_response(text, key) text.bytes.zip(key.bytes).reduce('') do |memo, pair| memo + (pair[0] ^ pair[1]).chr end end
Get token from given secret and timestamp @param secret [String] hexified secret @param timestamp [Integer] UNIX timestamp in seconds,
defaults to current time
@return [String, Integer] token and the next timestamp token to change
# File lib/bnet/authenticator.rb, line 105 def self.get_token(secret, timestamp = nil) secret = Bnet::Attributes::Secret.new secret current = (timestamp || Time.now.getutc.to_i) / 30 digest = Digest::HMAC.digest([current].pack('Q>'), secret.binary, Digest::SHA1) start_position = digest[19].ord & 0xf token = digest[start_position, 4].unpack('L>')[0] & 0x7fffffff return '%08d' % (token % 100000000), (current + 1) * 30 end
Create a new authenticator with given serial and secret @param serial [String] @param secret [String]
# File lib/bnet/authenticator.rb, line 34 def initialize(serial, secret) serial = Bnet::Attributes::Serial.new serial secret = Bnet::Attributes::Secret.new secret restorecode = Bnet::Attributes::Restorecode.new serial, secret @serial = serial.to_s @secret = secret.to_s @restorecode = restorecode.to_s @region = serial.region end
Request a new authenticator from server @param region [Symbol] @return [Bnet::Authenticator]
# File lib/bnet/authenticator.rb, line 48 def self.request_authenticator(region) k = create_one_time_pad(37) payload_plain = "\1" + k + region.to_s + CLIENT_MODEL.ljust(16, "\0")[0, 16] e = rsa_encrypt_bin(payload_plain) 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]) end
# File lib/bnet/authenticator/core.rb, line 28 def request_for(label, region, path, body = nil) raise BadInputError.new("bad region #{region}") unless AUTHENTICATOR_HOSTS.has_key? region 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
Get server's time @param region [Symbol] @return [Integer] server timestamp in seconds
# File lib/bnet/authenticator.rb, line 95 def self.request_server_time(region) server_time_big_endian = request_for('server time', region, TIME_REQUEST_PATH) server_time_big_endian.unpack('Q>')[0].to_f / 1000 end
Restore an authenticator from server @param serial [String] @param restorecode [String] @return [Bnet::Authenticator]
# File lib/bnet/authenticator.rb, line 65 def self.restore_authenticator(serial, restorecode) serial = Bnet::Attributes::Serial.new serial restorecode = Bnet::Attributes::Restorecode.new restorecode # stage 1 challenge = request_for('restore (stage 1)', serial.region, RESTORE_INIT_REQUEST_PATH, serial.normalized) # stage 2 key = create_one_time_pad(20) digest = Digest::HMAC.digest(serial.normalized + challenge, restorecode.binary, Digest::SHA1) payload = serial.normalized + rsa_encrypt_bin(digest + key) response_body = request_for('restore (stage 2)', serial.region, RESTORE_VALIDATE_REQUEST_PATH, payload) Authenticator.new(serial, decrypt_response(response_body, key)) end
# File lib/bnet/authenticator/core.rb, line 23 def rsa_encrypt_bin(bin) i = bin.unpack('C*').map{ |i| i.to_s(16).rjust(2, '0') }.join.to_i(16) (i ** RSA_KEY % RSA_MOD).to_s(16).scan(/.{2}/).map {|s| s.to_i(16)}.pack('C*') end
Public Instance Methods
Get authenticator's token from given timestamp @param timestamp [Integer] UNIX timestamp in seconds,
defaults to current time
@return [String, Integer] token and the next timestamp token to change
# File lib/bnet/authenticator.rb, line 121 def get_token(timestamp = nil) self.class.get_token(secret, timestamp) end
Hash representation of this authenticator @return [Hash]
# File lib/bnet/authenticator.rb, line 127 def to_hash { :serial => serial, :secret => secret, :restorecode => restorecode, :region => region, } end
# File lib/bnet/command.rb, line 5 def to_readable_text "Serial: #{serial}\nSecret: #{secret}\nRestoration Code: #{restorecode}" end
String representation of this authenticator @return [String]
# File lib/bnet/authenticator.rb, line 138 def to_s to_hash.to_s end