Class: SafeFile

Inherits:
Object
  • Object
show all
Defined in:
lib/safefile.rb

Overview

SafeFile The file containing the safe entries

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(password, dir, filename = '.safe.xml') ⇒ SafeFile

Returns a new instance of SafeFile.



47
48
49
50
51
52
# File 'lib/safefile.rb', line 47

def initialize(password, dir, filename = '.safe.xml')
  @entries = Hash.new
  @filename = "#{dir}/#{filename}"
  @password = password
  load
end

Instance Attribute Details

#entriesObject

Returns the value of attribute entries.



45
46
47
# File 'lib/safefile.rb', line 45

def entries
  @entries
end

#filenameObject

Returns the value of attribute filename.



45
46
47
# File 'lib/safefile.rb', line 45

def filename
  @filename
end

#hashObject

Returns the value of attribute hash.



45
46
47
# File 'lib/safefile.rb', line 45

def hash
  @hash
end

#passwordObject

Returns the value of attribute password.



45
46
47
# File 'lib/safefile.rb', line 45

def password
  @password
end

#saltObject

Returns the value of attribute salt.



45
46
47
# File 'lib/safefile.rb', line 45

def salt
  @salt
end

Instance Method Details

#add(name) ⇒ Object

Adds an entry



128
129
130
131
132
133
134
135
136
# File 'lib/safefile.rb', line 128

def add(name)
  if can_insert? name
    print "#{name} User ID: "
    id = gets.chomp!
    pw = SafeUtils::get_password("#{name} Password: ")
    insert(name, id, pw)
    save
  end
end

#authorized?Boolean

Returns whether the password is authorized

Returns:

  • (Boolean)


202
203
204
# File 'lib/safefile.rb', line 202

def authorized?
  Digest::SHA256.hexdigest(@password + @salt) == @hash
end

#can_insert?(name) ⇒ Boolean

Determines whether we can insert this entry–if it exists, we prompt for the OK

Returns:

  • (Boolean)


145
146
147
148
149
150
151
152
# File 'lib/safefile.rb', line 145

def can_insert?(name)
  proceed = true
  if @entries.has_key?(name)
    print "Overwrite existing #{name} (y/N)? "
    proceed = (gets.chomp == 'y')
  end
  proceed
end

#change_passwordObject

Changes the file’s password



184
185
186
187
188
189
190
191
192
193
194
# File 'lib/safefile.rb', line 184

def change_password
  new_password = SafeUtils::get_password('Enter new password: ')
  rpt_password = SafeUtils::get_password('Confirm password: ')
  if new_password == rpt_password
    @password = new_password
    generate_hash_and_salt
    save
  else
    puts 'Passwords do not match'
  end
end

#countObject

Returns the count of entries in the file



179
180
181
# File 'lib/safefile.rb', line 179

def count()
  @entries.length
end

#create_element(name, text) ⇒ Object

Helper method to create an XML element with a name and text



207
208
209
210
211
# File 'lib/safefile.rb', line 207

def create_element(name, text)
  e = Element.new name
  e.text = text
  e
end

#create_file(file) ⇒ Object

Creates a new file



98
99
100
101
102
# File 'lib/safefile.rb', line 98

def create_file(file)
  puts 'Creating new file . . .'
  generate_hash_and_salt
  save
end

#decrypt_file(file) ⇒ Object

Decrypts the file



72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/safefile.rb', line 72

def decrypt_file(file)
  begin
    doc = Document.new file
    root = doc.root
    @hash = root.elements["hash"].text
    @salt = root.elements["salt"].text
    if authorized?
      crypt_key = Crypt::Blowfish.new(@password)
      e_entries = root.elements["entries"]
      e_entries.elements.each("entry") do |entry|
        fields = crypt_key.decrypt_string(Base64::decode64(entry.cdatas[0].to_s)).split("\t")
        if fields.length == 3
          insert(fields[0], fields[1], fields[2])
        else
          puts "Cannot parse #{fields}, discarding."
        end
      end
    else
      abort('The password you entered is not valid')
    end
  rescue ParseException
    puts "Cannot parse #{file.path}"
  end
end

#delete(name) ⇒ Object

Deletes an entry



155
156
157
158
159
160
161
162
# File 'lib/safefile.rb', line 155

def delete(name)
  if @entries.has_key?(name)
    @entries.delete(name)
    save
  else
    puts "Entry #{name} not found"
  end
end

#generate_hash_and_saltObject



196
197
198
199
# File 'lib/safefile.rb', line 196

def generate_hash_and_salt
  @salt = [Array.new(20){rand(256).chr}.join].pack('m').chomp
  @hash = Digest::SHA256.hexdigest(@password + @salt)
end

#insert(name, id, pw) ⇒ Object

Inserts an entry



139
140
141
142
# File 'lib/safefile.rb', line 139

def insert(name, id, pw)
  @entries.delete(name)
  @entries[name] = SafeEntry.new(name, id, pw)
end

#list(name) ⇒ Object

Lists the desired entries



165
166
167
168
169
170
171
172
173
174
175
176
# File 'lib/safefile.rb', line 165

def list(name)
  output = Array.new
  @entries.each_value do |entry|
    # TODO Inconsistent case handling
    if name == nil || entry.name.upcase =~ /^#{name.upcase}/
      output << entry
    end
  end
  output.sort.each do |entry|
    puts entry
  end
end

#loadObject

Loads the file



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/safefile.rb', line 55

def load
  f = File.open(@filename, File::CREAT)
  begin
    if f.lstat.size == 0
        create_file(f)
    else
        decrypt_file(f)
    end
    rescue
      # TODO Determine whether error is can't create file, and print appropriate error
      puts $!
    ensure
      f.close unless f.nil?
  end
end

#saveObject

Saves the file



105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/safefile.rb', line 105

def save
  crypt_key = Crypt::Blowfish.new(@password)
  doc = Document.new
  root = Element.new "safe"
  doc.add_element root

  root.add_element create_element("hash", @hash)
  root.add_element create_element("salt", @salt)
  
  e_entries = Element.new "entries"
  @entries.each_value do |entry|
    e = Element.new "entry"
    CData.new Base64.encode64(crypt_key.encrypt_string(entry.to_s)), true, e
    e_entries.add_element e
  end
  root.add_element e_entries
  
  f = File.new(@filename, 'w')
  doc.write f, 0
  f.close
end