authentic 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8a5638eac32ad5d3c4da52d99a8c25ca5c2bdca5
4
- data.tar.gz: ca509f3b357a2d5a8cdffa593c6721f45adb72cf
3
+ metadata.gz: c3ab2e1fa925121b4404ff3f34e23a0e7e4e8437
4
+ data.tar.gz: 63809f5dc1296be67f369b143a8d1d95f3489f6f
5
5
  SHA512:
6
- metadata.gz: 17d8c8a27bb04f8c36cfdd4adbbffcfe4d797ceb3ac4581fcbcca60a7157629024f6b72ecdd890aff20e25f7458a2b291da2ab818c5ae7d44d3d10c21aa365d0
7
- data.tar.gz: 29649b4fdb4fe73a9193bd8a14d0b17956a886a81db9275854c480212402c7550d1c608455da30134191e16b32c879ec6de2ef4db9807608d6b7490bbe49aa51
6
+ metadata.gz: ee92948d013b885d18f911acd694b6ba803386e2640695530aa17365e53c7f3fe39fb75956beefa53f0cb52cd294024262bb301f2d2d7f64b7c510a67caee6ef
7
+ data.tar.gz: 6dc8572b0913523d15f26a9c226bceb993dcadeec9e930470c0f385a17ca0f0de01efa36ecd6ce4496489f6cc4995b53e2a506efb6e6ccae4a3c7689c45f5507
@@ -5,6 +5,9 @@ require "keychain"
5
5
  require "rotp"
6
6
  require "colorize"
7
7
  require "ostruct"
8
+ require "json"
9
+ require "openssl"
10
+ require "base64"
8
11
 
9
12
  module Authentic
10
13
  class CLI < Thor
@@ -37,7 +40,7 @@ module Authentic
37
40
  desc "delete NAME", "Delete a TOTP key"
38
41
  option :force, default: false, type: :boolean, aliases: '-f'
39
42
  def delete(name)
40
- item = Keychain.generic_passwords.where(service: "authentic gem", account: name).first
43
+ item = Keychain.generic_passwords.where(label: "authentic gem", account: name).first
41
44
  unless item
42
45
  return say "\u2717".colorize(:red) + " Couldn't find service #{name.colorize(:red)}..."
43
46
  end
@@ -57,9 +60,70 @@ module Authentic
57
60
  28 => "🌑",
58
61
  }
59
62
 
63
+ desc "export", "Export TOTP secret keys"
64
+ option 'qr', default: false, type: :boolean, aliases: '-q'
65
+ def export
66
+ keys = Keychain
67
+ .generic_passwords
68
+ .where(label: "authentic gem")
69
+ .all.map do |key|
70
+ secret = key.password.gsub(/=*$/, '')
71
+ totp = ROTP::TOTP.new(secret)
72
+ OpenStruct.new(
73
+ secret: secret,
74
+ name: key.attributes[:account],
75
+ service: key.attributes[:service]
76
+ )
77
+ end.sort_by { |k| [k.service, k.name] }
78
+
79
+ if options['qr']
80
+ keys.each do |key|
81
+ puts "#{key.service} - #{key.name}\n"
82
+ puts `qrencode 'otpauth://totp/#{key.name}?issuer=#{key.service}&secret=#{key.secret}' -s 5 -o - | ~/.iterm2/imgcat`
83
+ end
84
+ else
85
+ data = keys.map(&:to_h).to_json
86
+
87
+ password = ask "Please enter a password for exported data:", echo: false
88
+ return if password.empty?
89
+
90
+ salt = OpenSSL::Random.random_bytes(32)
91
+ cipher = OpenSSL::Cipher::AES256.new :CBC
92
+ cipher.encrypt
93
+ cipher.key = OpenSSL::PKCS5.pbkdf2_hmac_sha1(password, salt, 20000, 32)
94
+ cipher.iv = salt
95
+
96
+ cipher_text = cipher.update(data)
97
+ cipher_text << cipher.final
98
+
99
+ puts [salt, cipher_text].map { |part| Base64.strict_encode64(part) }.join(':')
100
+ end
101
+ end
102
+
103
+ desc "import DATA", "Import TOTP secret keys"
104
+ def import(data)
105
+ password = ask "Please enter a password for exported data:", echo: false
106
+ return if password.empty?
107
+
108
+ salt, cipher_text = data.split(':').map { |part| Base64.strict_decode64(part) }
109
+
110
+ cipher = OpenSSL::Cipher::AES256.new :CBC
111
+ cipher.decrypt
112
+ cipher.iv = salt
113
+ cipher.key = OpenSSL::PKCS5.pbkdf2_hmac_sha1(password, salt, 20000, 32)
114
+
115
+ text = cipher.update(cipher_text)
116
+ text << cipher.final
117
+
118
+ keys = JSON.parse(text)
119
+
120
+ keys.each do |key|
121
+ add(key['name'], key['secret'], key['service'])
122
+ end
123
+ end
124
+
60
125
  desc "generate", "Generate TOTP codes"
61
126
  option 'skip-copy', default: false, type: :boolean, aliases: '-s'
62
- option 'qr-codes', default: false, type: :boolean, aliases: '-qr'
63
127
  def generate
64
128
  now = Time.now
65
129
  keys = Keychain
@@ -77,14 +141,6 @@ module Authentic
77
141
  )
78
142
  end.sort_by { |k| [k.service, k.name] }
79
143
 
80
- if options['qr-codes']
81
- keys.each do |key|
82
- puts "#{key.service} - #{key.name}\n"
83
- puts `qrencode 'otpauth://totp/#{key.name}?issuer=#{key.service}&secret=#{key.secret}' -s 5 -o - | ~/.iterm2/imgcat`
84
- end
85
- return
86
- end
87
-
88
144
  table = keys.each_with_index.map do |key, idx|
89
145
  number = (idx + 1).to_s.rjust(keys.size.to_s.size, ' ')
90
146
  service_prefix = "#{key.service} - " if key.service && key.service != key.name
@@ -98,7 +154,7 @@ module Authentic
98
154
 
99
155
  print_table(table.to_a)
100
156
 
101
- unless options['skip-copy']
157
+ unless options['skip-copy'] || keys.size == 0
102
158
  if keys.size > 1
103
159
  prompt = "\nWhich key should I copy?"
104
160
  prompt += " [1-#{keys.size}, leave empty to exit]"
@@ -1,3 +1,3 @@
1
1
  module Authentic
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: authentic
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ufuk Kayserilioglu
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-09-01 00:00:00.000000000 Z
11
+ date: 2017-10-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: colorize