Created
November 24, 2015 22:48
-
-
Save aeris/fd59fd49205cb085374c to your computer and use it in GitHub Desktop.
Google Authenticator CLI
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env ruby | |
# Licence : AGPLv3+ | |
require 'cgi' | |
require 'fileutils' | |
require 'optparse' | |
require 'rotp' | |
require 'tempfile' | |
require 'uri' | |
class GoogleAuthenticator | |
CONFIG_FILE = File.join Dir.home, '.google-authenticator' | |
FileUtils.touch CONFIG_FILE unless File.exists? CONFIG_FILE | |
class OTP | |
attr_reader :name, :issuer | |
def self.get(line) | |
uri = URI line | |
type = uri.host | |
name = uri.path.sub /^\//, '' | |
query = CGI::parse uri.query | |
secret = query['secret'].first | |
issuer = query['issuer'].first | |
algorithm = query['algorithm'].first || 'sha1' | |
digits = numeric_value query, 'digits', 6 | |
counter = numeric_value query, 'counter' | |
period = numeric_value query, 'period', 30 | |
case type | |
when 'hotp' then | |
HOTP.new line, name, secret, issuer, algorithm, digits, counter | |
when 'totp' then | |
TOTP.new line, name, secret, issuer, algorithm, digits, period | |
end | |
end | |
def qrcode | |
file = ::Tempfile.new ['qrcode', 'png'] | |
system 'qrencode', '-t', 'png', '-o', file.path, @line | |
file | |
end | |
protected | |
def initialize(otp, line, name, secret, issuer=nil, algorithm='sha1', digits=6) | |
@otp = otp | |
@line = line | |
@name = name | |
@secret = secret | |
@issuer = issuer | |
@algorithm = algorithm | |
@digits = digits | |
end | |
private_class_method | |
def self.numeric_value(query, name, default_value = nil) | |
value = query[name].first | |
return default_value if value.nil? | |
value.to_i | |
end | |
end | |
class HOTP < OTP | |
def initialize(line, name, secret, issuer=nil, algorithm='sha1', digits=6, counter=0) | |
super line, name, secret, issuer, algorithm, digits | |
@counter = counter | |
end | |
end | |
class TOTP < OTP | |
def initialize(line, name, secret, issuer=nil, algorithm='sha1', digits=6, period=30) | |
@period = period | |
otp = ::ROTP::TOTP.new secret, digits: digits, digest: algorithm, interval: period | |
super otp, line, name, secret, issuer, algorithm, digits | |
end | |
def code | |
@otp.now | |
end | |
def delay | |
@period - (Time.now.to_i % @period) | |
end | |
end | |
attr_reader :otps | |
def initialize | |
@otps = Hash[IO.readlines(CONFIG_FILE).collect do |line| | |
otp = OTP.get line.chomp() | |
[otp.name, otp] | |
end] | |
end | |
def add(secret) | |
puts "Adding secret : \"#{secret}\"" | |
File.open(CONFIG_FILE, 'a') do |f| | |
f.puts secret | |
end | |
end | |
def add_qrcode(data) | |
require 'zbar' | |
self.add ZBar::Image.from_jpeg(data).process[0].data | |
end | |
def otp(name) | |
raise 'No such OTP' unless @otps.has_key? name | |
@otps[name] | |
end | |
def otps | |
@otps.collect do |_, o| | |
s = o.name | |
s << %{ (#{o.issuer})} unless o.issuer.nil? | |
s | |
end | |
end | |
end | |
begin | |
authenticator = GoogleAuthenticator.new | |
options = {} | |
OptionParser.new do |opts| | |
opts.on('-a STRING', '--add STRING', 'Add secret from string') do |o| | |
authenticator.add o | |
exit | |
end | |
opts.on('-i[PATH]', '--img[=PATH]', 'Add secret from QR code') do |o| | |
o = o.nil? ? $stdin : File.read(o) | |
authenticator.add_qrcode o | |
exit | |
end | |
opts.on('-d NAME', '--delete NAME', 'Remove secret') do |o| | |
exit | |
end | |
opts.on('-q NAME', '--qrcode NAME', 'Display qrcode') do |o| | |
file = authenticator.otp(o).qrcode | |
system 'display', file.path | |
file.close | |
file.unlink | |
exit | |
end | |
end.parse! | |
name = ARGV[0] | |
if name.nil? | |
puts authenticator.otps | |
else | |
otp = authenticator.otp name | |
unless $stdout.tty? | |
puts otp.code | |
$stderr.puts "#{otp.code} (#{otp.delay}s)" | |
else | |
puts "#{otp.code} (#{otp.delay}s)" | |
end | |
end | |
rescue Exception => e | |
puts e.message | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment