From 9529d2a2807f1c7a2c100d3de39e6e9e9c6b3b0d Mon Sep 17 00:00:00 2001 From: Nat Lasseter Date: Tue, 17 Dec 2019 09:44:22 +0000 Subject: Initial commit --- LICENSE | 25 +++++++++++++++++++ Readme.textile | 13 ++++++++++ twofa | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 114 insertions(+) create mode 100644 LICENSE create mode 100644 Readme.textile create mode 100755 twofa diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..70c63f6 --- /dev/null +++ b/LICENSE @@ -0,0 +1,25 @@ +BSD 2-Clause License + +Copyright (c) 2019, Nat Lasseter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/Readme.textile b/Readme.textile new file mode 100644 index 0000000..123c0d6 --- /dev/null +++ b/Readme.textile @@ -0,0 +1,13 @@ +h1. twofa + +p. A terrible, should-not-be-used, wrote-it-for-fun-and-learning 2fa authenticator. + +h2. Usage + +bc. $ twofa ISSUER + +h3. @~/.twofa@ + +p. The twofa rc file has the format: + +bc. ISSUER SECRET [interval | default(30)] [length | default(6)] [hashing algorithm | default(sha1)] diff --git a/twofa b/twofa new file mode 100755 index 0000000..01c6e0c --- /dev/null +++ b/twofa @@ -0,0 +1,76 @@ +#!/usr/bin/env ruby + +require "openssl" +require "base32" + +def db32(str) + Base32.decode(str) +end + +def hs1(key, td = 30, tx = Time.now.to_i, hsh = "sha1") + OpenSSL::HMAC.hexdigest(hsh, key.to_s, [tx.to_i/td.to_i].pack("Q>")) +end + +def dt(str) + offset = str[-1].to_i(16) + p = str[(offset*2)...((offset+4)*2)] + p[0] = (p[0].to_i(16) & 0x7).to_s(16) + p.to_i(16) +end + +def hotp(num, dig = 6) + num % (10 ** dig) +end + +def fatal(msg) + $stderr.puts(msg) + exit 1 +end + +class Secrets + class Secret + def initialize(secret, td, dig, hsh) + @secret = secret + @td = td + @dig = dig + @hsh = hsh + end + + def verify(tx = Time.now.to_i) + decoded = db32(@secret) + hmac = hs1(decoded, @td, tx, @hsh) + trunc = dt(hmac) + code = hotp(trunc, @dig) + "%0#{@dig}d" % code + end + + def time_remaining(tx = Time.now.to_i) + (tx.to_i/@td.to_i + 1) * @td - tx + end + end + + def initialize(arr) + @secrets = {} + arr.each do |secretline| + i, s, t, d, h = secretline.split + @secrets[i] = Secret.new(s, t&.to_i || 30, d&.to_i || 6, h || "sha1") + end + end + + def [](issuer) + @secrets[issuer] + end +end + +TWOFAFILE = File.join(ENV["HOME"], ".twofa") +fatal("No 2fa issuers file at ~/.twofa") unless File.exist?(TWOFAFILE) + +SECRETS = Secrets.new(File.readlines(TWOFAFILE).map(&:strip)) + +ISSUER = ARGV.shift&.strip&.downcase +fatal("Specify issuer") if ISSUER.nil? + +sec = SECRETS[ISSUER] +fatal("No such issuer") if sec.nil? + +puts "#{sec.verify} (for #{sec.time_remaining} more seconds)" -- cgit v1.2.1