class TheFox::OSP::OSP

Constants

HASHES_EXP
HASHES_N
ID
PASSWORD_MAX_SIZE
PASSWORD_MIN_SIZE
SYMBOLS

Attributes

dk[RW]
hashes[RW]
steps[R]

Public Class Methods

new(email, password, hashes = self.class::HASHES_N) click to toggle source
# File lib/osp/osp.rb, line 23
def initialize(email, password, hashes = self.class::HASHES_N)
  @email = email
  @password = password
  @hashes = hashes
  @dk = nil
  @password_callback_method = nil
  @steps = 0
end

Public Instance Methods

key_derivation() click to toggle source
# File lib/osp/osp.rb, line 32
def key_derivation
  @dk = OpenSSL::PKCS5.pbkdf2_hmac(@password, @email, @hashes, 64, OpenSSL::Digest::SHA512.new)
  # puts
  # puts 'DK: ' + Base64.strict_encode64(@dk)
  # puts
end
password(host_name, length = 16, generation = 1, symbols_n = self.class::SYMBOLS) click to toggle source
# File lib/osp/osp.rb, line 39
def password(host_name, length = 16, generation = 1, symbols_n = self.class::SYMBOLS)
  if length < PASSWORD_MIN_SIZE
    raise RangeError, 'Invalid password length: %d. Minimum length is %d.' % [length, PASSWORD_MIN_SIZE]
  end
  if length > PASSWORD_MAX_SIZE
    raise RangeError, 'Invalid password length: %d. Maximum length is %d.' % [length, PASSWORD_MAX_SIZE]
  end
  if host_name.nil? || host_name == '' || !host_name
    raise ArgumentError, "'host_name' can't be '' or nil"
  end
  
  if @dk.nil?
    key_derivation
  end
  
  password_s = find_password(host_name, generation)
  
  if symbols_n > 0
    password_s = find_symbols(password_s, symbols_n)
  end
  
  password_s[0...length]
end
password_callback_method=(m) click to toggle source
# File lib/osp/osp.rb, line 63
def password_callback_method=(m)
  @password_callback_method = m
end

Private Instance Methods

bad(x) click to toggle source
# File lib/osp/osp.rb, line 125
def bad(x)
  x == 0 || x > 5
end
find_method_to_sub(password_s) click to toggle source
# File lib/osp/osp.rb, line 166
def find_method_to_sub(password_s)
  caps = 0
  lowers = 0
  digits = 0
  
  (0...self.class::PASSWORD_MIN_SIZE).each do |n|
    c = password_s[n]
    if c.is_digit?
      digits += 1
    elsif c.is_upper?
      caps += 1
    elsif c.is_lower?
      lowers += 1
    end
  end
  
  if lowers >= caps && lowers >= digits then
    'is_lower?'
  elsif digits > lowers && digits >= caps
    'is_digit?'
  else
    'is_upper?'
  end
end
find_password(host_name, generation) click to toggle source
# File lib/osp/osp.rb, line 69
def find_password(host_name, generation)
  password_s = nil
  @steps = 0
  while password_s.nil?
    raw = [self.class::ID, @email, host_name, generation, @steps]
    data = raw.to_msgpack
    hmac_p = OpenSSL::HMAC.digest(OpenSSL::Digest::SHA512.new, @dk, data)
    hmac_b64 = Base64.strict_encode64(hmac_p)
    # puts 'b64 %s' % [hmac_b64]
    if is_password_ok(hmac_b64)
      password_s = hmac_b64
    end
    
    if not @password_callback_method.nil?
      @password_callback_method.call(@steps, hmac_b64)
    end
    @steps += 1
  end
  
  password_s
end
find_symbols(password_s, symbols_n) click to toggle source
# File lib/osp/osp.rb, line 91
def find_symbols(password_s, symbols_n)
  sub_method = find_method_to_sub(password_s)
  
  _b64map = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='
  
  indices = Array.new
  (0..self.class::PASSWORD_MIN_SIZE).each do |n|
    c = password_s[n]
    if c.method(sub_method).call
      indices << n
      if indices.count >= symbols_n
        break
      end
    end
  end
  
  _map = "`~!@#$%^&*()-_+={}[]|;:,<>.?/"
  _map_len = _map.length
  
  last = 0
  arr = Array.new
  indices.each do |index|
    arr << password_s[last...index]
    c = password_s[index]
    i = _b64map.index(c)
    x = _map[i % _map_len]
    arr << x
    last = index + 1
  end
  arr << password_s[last..-1]
  
  arr.join
end
is_password_ok(password_s) click to toggle source
# File lib/osp/osp.rb, line 129
def is_password_ok(password_s)
  caps = 0
  lowers = 0
  digits = 0
  
  # Get the entropy for the minium password length.
  (0...self.class::PASSWORD_MIN_SIZE).each do |n|
    c = password_s[n]
    
    if c.is_digit?
      digits += 1
    elsif c.is_upper?
      caps += 1
    elsif c.is_lower?
      lowers += 1
    else
      return false
    end
  end
  
  # Check minimum entropy.
  if bad(caps) || bad(lowers) || bad(digits)
    return false
  end
  
  # Also check minimum to maximum password size for later use.
  # Passwords can be extended later to maximum size without changing
  # the first characters.
  (self.class::PASSWORD_MIN_SIZE...self.class::PASSWORD_MAX_SIZE).each do |n|
    if not password_s[n].is_valid?
      return false
    end
  end
  
  true
end