authentic 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/authentic.rb +67 -11
- data/lib/authentic/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c3ab2e1fa925121b4404ff3f34e23a0e7e4e8437
|
4
|
+
data.tar.gz: 63809f5dc1296be67f369b143a8d1d95f3489f6f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ee92948d013b885d18f911acd694b6ba803386e2640695530aa17365e53c7f3fe39fb75956beefa53f0cb52cd294024262bb301f2d2d7f64b7c510a67caee6ef
|
7
|
+
data.tar.gz: 6dc8572b0913523d15f26a9c226bceb993dcadeec9e930470c0f385a17ca0f0de01efa36ecd6ce4496489f6cc4995b53e2a506efb6e6ccae4a3c7689c45f5507
|
data/lib/authentic.rb
CHANGED
@@ -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(
|
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]"
|
data/lib/authentic/version.rb
CHANGED
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.
|
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-
|
11
|
+
date: 2017-10-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: colorize
|