Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor Code #140

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions lib/rotp/arguments.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# frozen_string_literal: true

require 'optparse'
require 'ostruct'

Expand All @@ -14,7 +16,7 @@ def options
end

def to_s
parser.help + "\n"
"#{parser.help}\n"
end

private
Expand All @@ -33,9 +35,9 @@ def parse
return options!.mode = :help if arguments.empty?

parser.parse arguments
rescue OptionParser::InvalidOption => exception
rescue OptionParser::InvalidOption => e
options!.mode = :help
options!.warnings = red(exception.message + '. Try --help for help.')
options!.warnings = red("#{e.message}. Try --help for help.")
end

def parser
Expand All @@ -44,8 +46,8 @@ def parser
parser.separator green(' Usage: ') + bold("#{filename} [options]")
parser.separator ''
parser.separator green ' Examples: '
parser.separator ' ' + bold("#{filename} --secret p4ssword") + ' # Generates a time-based one-time password'
parser.separator ' ' + bold("#{filename} --hmac --secret p4ssword --counter 42") + ' # Generates a counter-based one-time password'
parser.separator " #{bold("#{filename} --secret p4ssword")} # Generates a time-based one-time password"
parser.separator " #{bold("#{filename} --hmac --secret p4ssword --counter 42")} # Generates a counter-based one-time password"
parser.separator ''
parser.separator green ' Options:'

Expand Down
37 changes: 19 additions & 18 deletions lib/rotp/base32.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# frozen_string_literal: true

require 'securerandom'

module ROTP
Expand All @@ -8,7 +10,6 @@ class Base32Error < RuntimeError; end
MASK = 31

class << self

def decode(str)
buffer = 0
idx = 0
Expand All @@ -17,13 +18,13 @@ def decode(str)
result = []
str.split('').each do |char|
buffer = buffer << SHIFT
buffer = buffer | (decode_quint(char) & MASK)
bits_left = bits_left + SHIFT
if bits_left >= 8
result[idx] = (buffer >> (bits_left - 8)) & 255
idx = idx + 1
bits_left = bits_left - 8
end
buffer |= (decode_quint(char) & MASK)
bits_left += SHIFT
next unless bits_left >= 8

result[idx] = (buffer >> (bits_left - 8)) & 255
idx += 1
bits_left -= 8
end
result.pack('c*')
end
Expand All @@ -34,35 +35,35 @@ def encode(b)
buffer = data[0]
idx = 1
bits_left = 8
while bits_left > 0 || idx < data.length
while bits_left.positive? || idx < data.length
if bits_left < SHIFT
if idx < data.length
buffer = buffer << 8
buffer = buffer | (data[idx] & 255)
bits_left = bits_left + 8
idx = idx + 1
buffer |= (data[idx] & 255)
bits_left += 8
idx += 1
else
pad = SHIFT - bits_left
buffer = buffer << pad
bits_left = bits_left + pad
bits_left += pad
end
end
val = MASK & (buffer >> (bits_left - SHIFT))
bits_left = bits_left - SHIFT
bits_left -= SHIFT
out.concat(CHARS[val])
end
return out
out
end

# Defaults to 160 bit long secret (meaning a 32 character long base32 secret)
def random(byte_length = 20)
rand_bytes = SecureRandom.random_bytes(byte_length)
self.encode(rand_bytes)
rand_bytes = SecureRandom.random_bytes(byte_length)
encode(rand_bytes)
end

# Prevent breaking changes
def random_base32(str_len = 32)
byte_length = str_len * 5/8
byte_length = str_len * 5 / 8
random(byte_length)
end

Expand Down
5 changes: 3 additions & 2 deletions lib/rotp/cli.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,10 @@ def output
return errors if errors
return arguments.to_s if options.mode == :help

if options.mode == :time
case options.mode
when :time
ROTP::TOTP.new(options.secret, options).now
elsif options.mode == :hmac
when :hmac
ROTP::HOTP.new(options.secret, options).at options.counter
end
end
Expand Down
19 changes: 10 additions & 9 deletions lib/rotp/otp.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# frozen_string_literal: true

module ROTP
class OTP
attr_reader :secret, :digits, :digest, :name, :issuer, :provisioning_params

DEFAULT_DIGITS = 6

# @param [String] secret in the form of base32
Expand All @@ -20,13 +23,13 @@ class OTP
# Additional non-standard params you may want appended to the
# provisioning URI. Ex. `image: 'https://example.com/icon.png'`
# @returns [OTP] OTP instantiation
def initialize(s, options = {})
def initialize(secret, options = {})
@digits = options[:digits] || DEFAULT_DIGITS
@digest = options[:digest] || 'sha1'
@name = options[:name]
@issuer = options[:issuer]
@provisioning_params = options[:provisioning_params] || {}
@secret = s
@secret = secret
end

# @param [Integer] input the number used seed the HMAC
Expand All @@ -44,8 +47,8 @@ def generate_otp(input)
(hmac[offset + 1].ord & 0xff) << 16 |
(hmac[offset + 2].ord & 0xff) << 8 |
(hmac[offset + 3].ord & 0xff)
code_str = (10 ** digits + (code % 10 ** digits)).to_s
code_str[-digits..-1]
code_str = (10**digits + (code % 10**digits)).to_s
code_str[-digits..]
end

private
Expand All @@ -66,12 +69,10 @@ def byte_secret
# along with the secret
#
def int_to_bytestring(int, padding = 8)
unless int >= 0
raise ArgumentError, '#int_to_bytestring requires a positive number'
end
raise ArgumentError, '#int_to_bytestring requires a positive number' unless int >= 0

result = []
until int == 0
until int.zero?
result << (int & 0xFF).chr
int >>= 8
end
Expand All @@ -85,7 +86,7 @@ def time_constant_compare(a, b)
l = a.unpack "C#{a.bytesize}"
res = 0
b.each_byte { |byte| res |= byte ^ l.shift }
res == 0
res.zero?
end
end
end
12 changes: 9 additions & 3 deletions lib/rotp/otp/uri.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# frozen_string_literal: true

module ROTP
class OTP
# https://github.com/google/google-authenticator/wiki/Key-Uri-Format
Expand All @@ -22,7 +24,7 @@ def algorithm

def counter
return if @otp.is_a?(TOTP)
fail if @counter.nil?
raise if @counter.nil?

@counter
end
Expand All @@ -45,15 +47,19 @@ def label
.join(':')
end

def parameters
def default_parameters_hash
{
secret: @otp.secret,
issuer: issuer,
algorithm: algorithm,
digits: digits,
period: period,
counter: counter,
counter: counter
}
end

def parameters
default_parameters_hash
.merge(@otp.provisioning_params)
.reject { |_, v| v.nil? }
.map { |k, v| "#{k}=#{ERB::Util.url_encode(v)}" }
Expand Down
6 changes: 4 additions & 2 deletions lib/rotp/totp.rb
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
# frozen_string_literal: true

module ROTP
DEFAULT_INTERVAL = 30
class TOTP < OTP
attr_reader :interval, :issuer

# @option options [Integer] interval (30) the time interval in seconds for OTP
# This defaults to 30 which is standard.
def initialize(s, options = {})
def initialize(secret, options = {})
@interval = options[:interval] || DEFAULT_INTERVAL
@issuer = options[:issuer]
super
Expand Down Expand Up @@ -69,7 +71,7 @@ def get_timecodes(at, drift_behind, drift_ahead)

# Ensure UTC int
def timeint(time)
return time.to_i unless time.class == Time
return time.to_i unless time.instance_of?(Time)

time.utc.to_i
end
Expand Down