passvault 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +3 -0
- data/Laurent_Vansteenberghe.zip +0 -0
- data/README.md +65 -0
- data/bin/passvault +3 -0
- data/bin/passvault-gui +3 -0
- data/lib/bytes_manip.rb +47 -0
- data/lib/conn_cmds.rb +337 -0
- data/lib/conn_constants.rb +116 -0
- data/lib/conn_init.rb +92 -0
- data/lib/conn_transmit.rb +96 -0
- data/lib/connection.rb +28 -0
- data/lib/console_ui.rb +243 -0
- data/lib/crypto.rb +146 -0
- data/lib/file_access.rb +103 -0
- data/lib/gui.rb +270 -0
- data/lib/key_settings.rb +124 -0
- data/lib/rights.rb +6 -0
- data/lib/vault.rb +357 -0
- data/passvault.gemspec +30 -0
- data/pres/secu.pdf +0 -0
- data/pres/secu.pptx +0 -0
- metadata +136 -0
@@ -0,0 +1,116 @@
|
|
1
|
+
# Uncommented constants are DESFire comments.
|
2
|
+
class Connection
|
3
|
+
# This file holds the constants used for data transmission and commands.
|
4
|
+
#
|
5
|
+
# In another language, we would have spread the constants accross multiple
|
6
|
+
# classes (for the card, chip and reader constants) and then imported those
|
7
|
+
# constants. But in Ruby doing so might cause the constants to silently
|
8
|
+
# collide. Putting them in the same class enables warnings in case of constant
|
9
|
+
# redefinition.
|
10
|
+
|
11
|
+
# ============================================================================
|
12
|
+
# DESFire (card) related constants
|
13
|
+
# ============================================================================
|
14
|
+
|
15
|
+
# fixed fields values
|
16
|
+
|
17
|
+
# CLA field for DESFire commands.
|
18
|
+
CARD_CLA = 0x90
|
19
|
+
|
20
|
+
# SW1 field for DESFire answers.
|
21
|
+
SW1 = 0x91
|
22
|
+
|
23
|
+
# Hash from DESFire response codes name (symbols) to the codes (integers).
|
24
|
+
STATUS = {
|
25
|
+
:OPERATION_OK => 0x00,
|
26
|
+
:ADDITIONAL_FRAME => 0xAF,
|
27
|
+
:NO_CHANGES => 0x0C,
|
28
|
+
:ILLEGAL_COMMAND_CODE => 0x1C,
|
29
|
+
:INTEGRITY_ERROR => 0x1E,
|
30
|
+
:NO_SUCH_KEY => 0x40,
|
31
|
+
:LENGTH_ERROR => 0x7E,
|
32
|
+
:PERMISSION_DENIED => 0x9D,
|
33
|
+
:PARAMETER_ERROR => 0x9E,
|
34
|
+
:APPLICATION_NOT_FOUND => 0xA0,
|
35
|
+
:APPLICATION_INTEGRITY_ERROR => 0xA1,
|
36
|
+
:AUTHENTICATION_ERROR => 0xAE,
|
37
|
+
:BOUNDARY_ERROR => 0xBE,
|
38
|
+
:COMMAND_ABORTED => 0xCA,
|
39
|
+
:DUPLICATE_ERROR => 0xDE,
|
40
|
+
:FILE_NOT_FOUND => 0xF0,
|
41
|
+
:OUT_OF_EEPROM_ERROR => 0x0E,
|
42
|
+
}
|
43
|
+
|
44
|
+
CHANGE_KEY = 0xC4
|
45
|
+
CREATE_APPLICATION = 0xCA
|
46
|
+
CREATE_STD_DATA_FILE = 0xCD
|
47
|
+
GET_CIPHERED_NONCE = 0x0A
|
48
|
+
SELECT_APPLICATION = 0x5A
|
49
|
+
SEND_MORE_DATA = 0xAF
|
50
|
+
FORMAT_PICC = 0xFC
|
51
|
+
GET_APP_IDS = 0x6A
|
52
|
+
GET_FILE_IDS = 0x6F
|
53
|
+
DELETE_APP = 0xDA
|
54
|
+
DELETE_FILE = 0xDF
|
55
|
+
READ_DATA = 0xBD
|
56
|
+
WRITE_DATA = 0x3D
|
57
|
+
GET_KEY_SETTINGS = 0x45
|
58
|
+
GET_FILE_SETTINGS = 0xF5
|
59
|
+
GET_VERSION = 0x60
|
60
|
+
CHANGE_KEY_SETTINGS = 0x54
|
61
|
+
|
62
|
+
# Padding beginning marker when reading a whole file with encrypted
|
63
|
+
# communication.
|
64
|
+
WHOLE_FILE_PAD_MARKER = "\x80"
|
65
|
+
|
66
|
+
# ============================================================================
|
67
|
+
# PN532 (chip) related constants
|
68
|
+
# ============================================================================
|
69
|
+
|
70
|
+
# Communication direction: send data to the chip.
|
71
|
+
TO_CHIP = 0xD4
|
72
|
+
|
73
|
+
# Communicaiton direction: receive data from the chip.
|
74
|
+
FROM_CHIP = 0xD5
|
75
|
+
|
76
|
+
# PN532 command: poll for tags.
|
77
|
+
LIST_PASSIVE_TARGET = 0x4A
|
78
|
+
|
79
|
+
# PN532 command: send data to/from the chip.
|
80
|
+
DATA_EXCHANGE = 0x40
|
81
|
+
|
82
|
+
# PN532 response code (prefix).
|
83
|
+
LIST_RESPONSE = 0x4B
|
84
|
+
|
85
|
+
# PN532 data response codes (array) (prefix).
|
86
|
+
DATA_RESPONSE = [0x41, 0x00]
|
87
|
+
|
88
|
+
# PN532 success response codes (array) (suffix).
|
89
|
+
SUCCESS_SUFFIX = [0x90, 0x00]
|
90
|
+
|
91
|
+
# ============================================================================
|
92
|
+
# ACR112 (reader) releated constants
|
93
|
+
# ============================================================================
|
94
|
+
|
95
|
+
# ACR112: class field for pseudo-APDU commands.
|
96
|
+
READER_CLA = 0xFF
|
97
|
+
|
98
|
+
# ACR112 pseudo-APDU commands: send data.
|
99
|
+
DIRECT_TRANSMIT = 0x00
|
100
|
+
|
101
|
+
# ACR112 pseudo-APDU commands: retrieve an answer.
|
102
|
+
GET_RESPONSE = 0xC0
|
103
|
+
|
104
|
+
# ACR112 response code (SW1 field).
|
105
|
+
SUCCESS = 0x61
|
106
|
+
|
107
|
+
# ACR112 response code (SW1 field).
|
108
|
+
ERROR = 0x63
|
109
|
+
|
110
|
+
# ACR112 response codes (SW2 field for SW1=ERROR).
|
111
|
+
OPERATION_FAILED = 0x00
|
112
|
+
|
113
|
+
# ACR112 response codes (SW2 field for SW1=ERROR).
|
114
|
+
NO_ANSWER = 0x01
|
115
|
+
|
116
|
+
end
|
data/lib/conn_init.rb
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
class Connection
|
2
|
+
# This files contains logic related to the initialization and termination of the
|
3
|
+
# connection.
|
4
|
+
|
5
|
+
# Connect to a card trough a reader.
|
6
|
+
def initialize()
|
7
|
+
super({}) # no options
|
8
|
+
|
9
|
+
@context = PCSC::Context.new
|
10
|
+
|
11
|
+
# find readers
|
12
|
+
begin
|
13
|
+
reader_names = @context.readers
|
14
|
+
rescue Exception => e
|
15
|
+
puts "No smartcard readers were detected."
|
16
|
+
disconnect
|
17
|
+
exit
|
18
|
+
end
|
19
|
+
|
20
|
+
# select a single reader
|
21
|
+
if reader_names.length == 1
|
22
|
+
@reader_name = reader_names.first
|
23
|
+
else
|
24
|
+
puts "Multiple readers available, please select one by number."
|
25
|
+
@reader_name = nil
|
26
|
+
while @reader_name == nil
|
27
|
+
reader_names.each_with_index do |r,i|
|
28
|
+
puts "#{i}) #{r.strip}"
|
29
|
+
end
|
30
|
+
begin
|
31
|
+
@reader_name = reader_names[gets.strip.to_i]
|
32
|
+
rescue
|
33
|
+
puts "Invalid selection."
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# The following two lines are lifted from the supermethod, since they were
|
39
|
+
# the only thing of interest there, due to idiosyncraties of the ACR122.
|
40
|
+
|
41
|
+
# The card object is used to transmit data, tough we don't need to use it
|
42
|
+
# directly, as data transmission is wrapped by the exchange_apdu(apdu)
|
43
|
+
# function.
|
44
|
+
|
45
|
+
@card = @context.card(@reader_name, :shared)
|
46
|
+
|
47
|
+
# the Answer To Reset for the card
|
48
|
+
@atr = @card.info[:atr]
|
49
|
+
|
50
|
+
# Wait until a card is inserted in the reader. Normally the super() call
|
51
|
+
# takes care of this, but the ACR122 is a broken beast which always
|
52
|
+
# indicates that a card is present, even if it isn't the case.
|
53
|
+
|
54
|
+
msg_displayed = false
|
55
|
+
while true
|
56
|
+
begin
|
57
|
+
poll
|
58
|
+
break
|
59
|
+
rescue Smartcard::PCSC::Exception => e
|
60
|
+
status = e.pcsc_status_code
|
61
|
+
if status == Smartcard::PCSC::FFILib::Status[:comm_data_lost]
|
62
|
+
puts "No cards detected, please insert your card." if !msg_displayed
|
63
|
+
msg_displayed = true
|
64
|
+
else
|
65
|
+
puts "error: #{e.message}"
|
66
|
+
disconnect
|
67
|
+
exit
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# Search for a card. This succeeds if a card is detected, else it throws a
|
74
|
+
# <tt>Smartcard::PCSC::Exception</tt> with +pcsc_status_code+ field set to the
|
75
|
+
# <tt>:comm_data_lost</tt> status after some time.
|
76
|
+
def poll(max_tags = 1, baud = 0)
|
77
|
+
response = send_chip_cmd LIST_PASSIVE_TARGET, [max_tags, baud]
|
78
|
+
throw :protocol_error unless response[0] == LIST_RESPONSE
|
79
|
+
end
|
80
|
+
|
81
|
+
# Release all resources associated with the card.
|
82
|
+
def disconnect
|
83
|
+
# :unpower is necessary if we want to relaunch the program without unpluging
|
84
|
+
# the reader.
|
85
|
+
unless @card.nil?
|
86
|
+
@card.disconnect(:unpower)
|
87
|
+
@card = nil
|
88
|
+
end
|
89
|
+
super
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require_relative 'crypto'
|
2
|
+
|
3
|
+
class Connection
|
4
|
+
# This file holds the implementation of the communication logic between the
|
5
|
+
# computer and the card. There are three "hardware" levels we need to go
|
6
|
+
# trought in order to send data to the card: the reader, the chip and the card
|
7
|
+
# itself. However the whole picture is a little more complex and looks like
|
8
|
+
# this:
|
9
|
+
#
|
10
|
+
# +---------------------------------------------------------------+
|
11
|
+
# | card_cmd -> card_apdu -> chip_cmd -> chip_apdu -> reader_apdu |
|
12
|
+
# +---------------------------------------------------------------+
|
13
|
+
#
|
14
|
+
# To each of the arrows in the diagram above correspond a method named
|
15
|
+
# send_<thing_before_the_arrow>. The function transforms the thing before the
|
16
|
+
# arrow into the thing after the arrow and passes it along the
|
17
|
+
# chain. Afterwards it gets a response for the thing after the arrow and
|
18
|
+
# transforms it into a response for the thing before the arrow, which is then
|
19
|
+
# returned.
|
20
|
+
#
|
21
|
+
# Transforming the responses usually consists of stripping some response codes
|
22
|
+
# from the output.
|
23
|
+
|
24
|
+
include Crypto
|
25
|
+
|
26
|
+
# Thrown when the contact with the card is lost.
|
27
|
+
CARD_CONTACT_LOST_ERROR =
|
28
|
+
'Contact with the tag was lost, please verify that the card is in the ' \
|
29
|
+
'reader, then relaunch the application.'
|
30
|
+
|
31
|
+
# Send a chip APDU trough the reader (by wrapping the chip APDU inside a
|
32
|
+
# reader pseudo-APDU) and return the chip response. This takes care of the
|
33
|
+
# reader response codes.
|
34
|
+
def send_chip_apdu(chip_apdu)
|
35
|
+
nbytes = chip_apdu.length
|
36
|
+
pseudo_apdu = [READER_CLA, DIRECT_TRANSMIT, 0x00, 0x00, nbytes, *chip_apdu]
|
37
|
+
reader_response = exchange_apdu(pseudo_apdu)
|
38
|
+
|
39
|
+
# check the reader response
|
40
|
+
case reader_response[0]
|
41
|
+
when ERROR
|
42
|
+
throw :operation_failed if reader_response[1] == OPERATION_FAILED
|
43
|
+
throw :no_answer if reader_response[1] == NO_ANSWER
|
44
|
+
throw :unknown_error
|
45
|
+
when SUCCESS
|
46
|
+
# fetch the actual card/chip response
|
47
|
+
fetch_len = reader_response[1]
|
48
|
+
pseudo_apdu = [READER_CLA, GET_RESPONSE, 0x00, 0x00, fetch_len]
|
49
|
+
exchange_apdu(pseudo_apdu)
|
50
|
+
else
|
51
|
+
throw :protocol_error
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Send a chip command and return the command response (not comprising the chip
|
56
|
+
# response codes).
|
57
|
+
#
|
58
|
+
# There are two useful commands: +DATA_EXCHANGE+ (used in send_car_apdu()) and
|
59
|
+
# +LIST_PASSIVE_TARGET+ (used by <tt>poll()</tt>).
|
60
|
+
def send_chip_cmd(cmd, args)
|
61
|
+
chip_apdu = [TO_CHIP, cmd, *args]
|
62
|
+
response = send_chip_apdu(chip_apdu)
|
63
|
+
len = response.length
|
64
|
+
throw :protocol_error unless
|
65
|
+
response[0] == FROM_CHIP &&
|
66
|
+
response[len-2..len-1] == SUCCESS_SUFFIX
|
67
|
+
return response[1..len-3]
|
68
|
+
end
|
69
|
+
|
70
|
+
# Send a card APDU to the card and return the card's answer. This takes care
|
71
|
+
# of chip command DATA_EXCHANGE return codes.
|
72
|
+
def send_card_apdu(card_apdu, tag_no=1)
|
73
|
+
response = send_chip_cmd(DATA_EXCHANGE, [tag_no, *card_apdu])
|
74
|
+
raise CARD_CONTACT_LOST_ERROR unless response[0..1] == DATA_RESPONSE
|
75
|
+
return response[2..response.length-1]
|
76
|
+
end
|
77
|
+
|
78
|
+
# Send a command to the card and return an array whose first element is the
|
79
|
+
# command response (not comprising the card response codes) and the second is
|
80
|
+
# a successful status (needed to know if additional frames are available).
|
81
|
+
def send_card_cmd(cmd, args=[])
|
82
|
+
apdu = args.length == 0 \
|
83
|
+
? [CARD_CLA, cmd, 0x00, 0x00, 0x00]
|
84
|
+
: [CARD_CLA, cmd, 0x00, 0x00, args.length, *args, 0x00]
|
85
|
+
response = send_card_apdu(apdu)
|
86
|
+
len = response.length
|
87
|
+
throw :protocol_error unless response[len-2] == SW1
|
88
|
+
status = response[len-1]
|
89
|
+
throw STATUS.key(status), true unless
|
90
|
+
status == STATUS[:OPERATION_OK] ||
|
91
|
+
status == STATUS[:ADDITIONAL_FRAME]
|
92
|
+
|
93
|
+
return [len < 3 ? [] : response[0..len-3], status]
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
data/lib/connection.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'smartcard'
|
2
|
+
|
3
|
+
# An instance of the class Connection represents a connection to a DESFire tag
|
4
|
+
# trough an ACR122 reader outfitted with PN532 chip. In our passworld vault
|
5
|
+
# program there is only one instance of this class.
|
6
|
+
#
|
7
|
+
# The class extends the class Smartcard::Iso::PcscTransport from which it
|
8
|
+
# inherits most notably the method <tt>exchange_apdu(apdu)</tt> which is used to
|
9
|
+
# send APDUs (Application Protocol Data Unit) to the reader.
|
10
|
+
#
|
11
|
+
# We found out that object-oriented decomposition didn't work well to model the
|
12
|
+
# mechanisms of data transmission to the card, as it is more functional in
|
13
|
+
# nature. We elected to model inside this single class. To improve readability,
|
14
|
+
# the class is split inside multiple files that cover different concerns. The
|
15
|
+
# files are described below in the <tt>connection.rb</tt> file.
|
16
|
+
class Connection < Smartcard::Iso::PcscTransport ; end
|
17
|
+
|
18
|
+
# Initialization and termination of the connection.
|
19
|
+
require_relative 'conn_init'
|
20
|
+
|
21
|
+
# Constants used for data transmission and commands.
|
22
|
+
require_relative 'conn_constants'
|
23
|
+
|
24
|
+
# Tranmission of APDUs at various levels (reader, chip, card).
|
25
|
+
require_relative 'conn_transmit'
|
26
|
+
|
27
|
+
# Functions wrapping commands to be sent to the card.
|
28
|
+
require_relative 'conn_cmds'
|
data/lib/console_ui.rb
ADDED
@@ -0,0 +1,243 @@
|
|
1
|
+
require 'clipboard'
|
2
|
+
require 'highline/import'
|
3
|
+
require 'smartcard'
|
4
|
+
|
5
|
+
require_relative 'vault'
|
6
|
+
|
7
|
+
class ConsoleUI
|
8
|
+
|
9
|
+
# Displayed when running the console program.
|
10
|
+
HEADER = "
|
11
|
+
_____ _
|
12
|
+
| _ |___ ___ ___ _ _ _ ___ ___ _| |
|
13
|
+
| __| .'|_ -|_ -| | | | . | _| . |
|
14
|
+
|__| |__,|___|___|_____|___|_| |___|
|
15
|
+
|
16
|
+
|
17
|
+
_____ _ _
|
18
|
+
| | |___ _ _| | |_
|
19
|
+
| | | .'| | | | _|
|
20
|
+
\\___/|__,|___|_|_|
|
21
|
+
|
22
|
+
|
23
|
+
(Type \"help\" to display available commands.)"
|
24
|
+
|
25
|
+
# Time after which the the console program forgets the vault master password
|
26
|
+
# and the associated key.
|
27
|
+
TIMEOUT = 300
|
28
|
+
|
29
|
+
# The console program entry point.
|
30
|
+
def self.run
|
31
|
+
ui = ConsoleUI.new()
|
32
|
+
ui.loop()
|
33
|
+
rescue Smartcard::PCSC::Exception => e
|
34
|
+
case e.pcsc_status
|
35
|
+
when :reader_unavailable
|
36
|
+
puts "\nThe reader was disconnected or is otherwise unavailable. " \
|
37
|
+
"You can't disconnect the card during the execution of the program, " \
|
38
|
+
"even if you reconnect it before executing a command."
|
39
|
+
else
|
40
|
+
puts "\n#{e.message()}"
|
41
|
+
end
|
42
|
+
rescue Exception => e
|
43
|
+
puts "\n#{e.message()}"
|
44
|
+
ensure
|
45
|
+
ui.leave() if ui
|
46
|
+
end
|
47
|
+
|
48
|
+
# Loop on user input (commands).
|
49
|
+
def loop
|
50
|
+
puts HEADER
|
51
|
+
@continue = true
|
52
|
+
while @continue
|
53
|
+
if catch :timeout do
|
54
|
+
command = request('> ')
|
55
|
+
execute(command.strip)
|
56
|
+
false
|
57
|
+
end then
|
58
|
+
@vault.deauthenticate()
|
59
|
+
puts 'The prompt timed out, please re-do the last command.'
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# Exit the console program.
|
65
|
+
def leave
|
66
|
+
@vault.destroy()
|
67
|
+
end
|
68
|
+
|
69
|
+
private ######################################################################
|
70
|
+
|
71
|
+
# Initialize the UI by creating a new +Vault+ object.
|
72
|
+
def initialize
|
73
|
+
@vault = Vault.new()
|
74
|
+
end
|
75
|
+
|
76
|
+
# Display the help of the console program.
|
77
|
+
def help()
|
78
|
+
puts ''
|
79
|
+
puts '-- user commands --'
|
80
|
+
puts 'list : list the names of registered credentials'
|
81
|
+
puts 'display <name> : display a registered credential'
|
82
|
+
puts 'clip <name> : same as "display", but copies the password'
|
83
|
+
puts ' to the clipboard instead of displaying it'
|
84
|
+
puts 'add <name> : register a new credential'
|
85
|
+
puts 'remove <name> : remove a registered credential'
|
86
|
+
puts 'edit <name> : change a registered credential'
|
87
|
+
puts 'quit : exit the program'
|
88
|
+
puts 'help : display this help message'
|
89
|
+
puts ''
|
90
|
+
puts '-- administration commands --'
|
91
|
+
puts 'reset : reinitialize the vault'
|
92
|
+
puts 'erase : erase the vault from the tag'
|
93
|
+
puts ''
|
94
|
+
end
|
95
|
+
|
96
|
+
# Ask the user for input, and throws <tt>:timeout</tt> (with value +true+) if
|
97
|
+
# the user is inactive for more than +TIMEOUT+ seconds.
|
98
|
+
def request(prompt, &block)
|
99
|
+
time = Time.now.to_i
|
100
|
+
answer = ask("\n#{prompt}", &block)
|
101
|
+
time = Time.now.to_i - time
|
102
|
+
throw :timeout, true if time > TIMEOUT
|
103
|
+
answer
|
104
|
+
end
|
105
|
+
|
106
|
+
# Prompt the user for a password. The password won't show on the screen (like
|
107
|
+
# in the Unix login prompt).
|
108
|
+
def enter_pass(prompt)
|
109
|
+
request(prompt) do |p| p.echo = false end
|
110
|
+
end
|
111
|
+
|
112
|
+
# Prompt for the tag's master password. Allows for a special case if the
|
113
|
+
# password is left empty.
|
114
|
+
def enter_tag_pass
|
115
|
+
master_prompt = 'Enter the tag master password (leave empty for default): '
|
116
|
+
pass = enter_pass(master_prompt)
|
117
|
+
pass = :default if pass == ""
|
118
|
+
return pass
|
119
|
+
end
|
120
|
+
|
121
|
+
# If not authenticated with the vault, prompt for the vault pass and perform
|
122
|
+
# the authentication.
|
123
|
+
def check_vault_pass
|
124
|
+
return true if @vault.authenticated?()
|
125
|
+
pass = enter_pass(
|
126
|
+
"Enter the vault master password \n" \
|
127
|
+
"(you will need to re-enter it after 5 minutes of inactivity): ")
|
128
|
+
@vault.authenticate(pass)
|
129
|
+
return true
|
130
|
+
rescue => e
|
131
|
+
return handle_auth_error(e)
|
132
|
+
end
|
133
|
+
|
134
|
+
# Re-raise the exception if it is not <tt>Vault::AUTHENTICATION_ERROR</tt>,
|
135
|
+
# else display the approrpiate message and return false.
|
136
|
+
def handle_auth_error(exception)
|
137
|
+
raise exception unless exception.message == Vault::AUTHENTICATION_ERROR
|
138
|
+
puts "\n#{exception.message}"
|
139
|
+
return false
|
140
|
+
end
|
141
|
+
|
142
|
+
# Execute a command entered by the user.
|
143
|
+
def execute(command)
|
144
|
+
# The regex will set $1 to the credential name.
|
145
|
+
name_regex = '(?<name>[[:graph:]]*)'
|
146
|
+
case command
|
147
|
+
when 'erase' ; erase()
|
148
|
+
when 'create', 'reset' ; reset()
|
149
|
+
when 'help' ; help()
|
150
|
+
when 'quit' ; @continue = false
|
151
|
+
when 'list' ; list()
|
152
|
+
when /display #{name_regex}/ ; display($1)
|
153
|
+
when /clip #{name_regex}/ ; clip($1)
|
154
|
+
when /add #{name_regex}/ ; add($1)
|
155
|
+
when /remove #{name_regex}/ ; remove($1)
|
156
|
+
when /edit #{name_regex}/ ; edit($1)
|
157
|
+
else ; unknown(command)
|
158
|
+
end
|
159
|
+
rescue Exception => e
|
160
|
+
raise e unless e.message == Vault::UNKNOWN_NAME_ERROR
|
161
|
+
puts "\n#{e.message}"
|
162
|
+
end
|
163
|
+
|
164
|
+
# See <tt>help()</tt> for a description.
|
165
|
+
def erase()
|
166
|
+
tag_pass = enter_tag_pass()
|
167
|
+
@vault.erase(tag_pass)
|
168
|
+
rescue => e
|
169
|
+
return handle_auth_error(e)
|
170
|
+
end
|
171
|
+
|
172
|
+
# See <tt>help()</tt> for a description.
|
173
|
+
def reset()
|
174
|
+
tag_pass = enter_tag_pass()
|
175
|
+
return unless tag_pass
|
176
|
+
vault_pass = enter_pass('Enter the new vault pass: ')
|
177
|
+
@vault.reset(tag_pass, vault_pass)
|
178
|
+
rescue => e
|
179
|
+
return handle_auth_error(e)
|
180
|
+
end
|
181
|
+
|
182
|
+
# See <tt>help()</tt> for a description.
|
183
|
+
def list()
|
184
|
+
return unless check_vault_pass()
|
185
|
+
puts "\nList of available credentials:"
|
186
|
+
@vault.credentials_names().each { |name| puts "- #{name}" }
|
187
|
+
end
|
188
|
+
|
189
|
+
# See <tt>help()</tt> for a description.
|
190
|
+
def display(name)
|
191
|
+
return unless check_vault_pass()
|
192
|
+
login, pass = @vault.credential(name)
|
193
|
+
puts ""
|
194
|
+
puts "login: #{login}"
|
195
|
+
puts "pass: #{pass}"
|
196
|
+
end
|
197
|
+
|
198
|
+
# See <tt>help()</tt> for a description.
|
199
|
+
def clip(name)
|
200
|
+
return unless check_vault_pass()
|
201
|
+
login, pass = @vault.credential(name)
|
202
|
+
puts ""
|
203
|
+
puts "login name: #{login}"
|
204
|
+
Clipboard.copy(pass)
|
205
|
+
end
|
206
|
+
|
207
|
+
# See <tt>help()</tt> for a description.
|
208
|
+
def add(name)
|
209
|
+
return unless check_vault_pass()
|
210
|
+
login = request('Enter a login: ')
|
211
|
+
pass = enter_pass('Enter a password: ')
|
212
|
+
@vault.add(name, login, pass)
|
213
|
+
rescue Vault::CredentialError => e
|
214
|
+
puts "\n#{e.message}"
|
215
|
+
end
|
216
|
+
|
217
|
+
# See <tt>help()</tt> for a description.
|
218
|
+
def remove(name)
|
219
|
+
return unless check_vault_pass()
|
220
|
+
@vault.remove(name)
|
221
|
+
end
|
222
|
+
|
223
|
+
# See <tt>help()</tt> for a description.
|
224
|
+
def edit(name)
|
225
|
+
return unless check_vault_pass()
|
226
|
+
login = request('Enter a login: ')
|
227
|
+
pass = enter_pass('Enter a new password: ')
|
228
|
+
@vault.edit(name, login, pass)
|
229
|
+
rescue Vault::CredentialError => e
|
230
|
+
puts "\n#{e.message}"
|
231
|
+
end
|
232
|
+
|
233
|
+
# Called when an unknown command is entered.
|
234
|
+
def unknown(command)
|
235
|
+
puts "\nUnknown command: #{command}"
|
236
|
+
puts 'Type "help" to display available commands.'
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
# # entry point
|
241
|
+
# if __FILE__ == $PROGRAM_NAME
|
242
|
+
# ConsoleUI.run
|
243
|
+
# end
|