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.



52
53
54
55
56
57
58
# File 'lib/safefile.rb', line 52

def initialize(password, dir, filename = '.safe.xml')
  raise "Password must not be blank" unless password
  self.entries = Hash.new
  self.filename = "#{dir}/#{filename}"
  self.password = password
  load
end

Instance Attribute Details

#entriesObject

Returns the value of attribute entries.



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

def entries
  @entries
end

#filenameObject

Returns the value of attribute filename.



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

def filename
  @filename
end

#passwordObject

Returns the value of attribute password.



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

def password
  @password
end

Instance Method Details

#add(name) ⇒ Object

Adds an entry



124
125
126
127
128
129
130
131
132
# File 'lib/safefile.rb', line 124

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)


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

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

#can_insert?(name) ⇒ Boolean

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

Returns:

  • (Boolean)


141
142
143
144
145
146
147
148
# File 'lib/safefile.rb', line 141

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



180
181
182
183
184
185
186
187
188
189
190
# File 'lib/safefile.rb', line 180

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

#countObject

Returns the count of entries in the file



175
176
177
# File 'lib/safefile.rb', line 175

def count()
  @entries.length
end

#create_element(name, text) ⇒ Object

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



203
204
205
206
207
# File 'lib/safefile.rb', line 203

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

#create_file(file) ⇒ Object

Creates a new file



95
96
97
98
99
# File 'lib/safefile.rb', line 95

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

#decrypt_file(file) ⇒ Object

Decrypts the file



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

def decrypt_file(file)
  begin
    root = Document.new(file).root
    @salt = root.attributes["salt"]
    @hash = root.attributes["hash"]
    if authorized?
      crypt_key = Crypt::Blowfish.new(self.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")
        fields.length == 3 ? insert(fields[0], fields[1], fields[2]) : puts("Cannot parse #{fields}, discarding.")
      end
    else
      raise 'The password you entered is not valid'
    end
  rescue ParseException
    puts "Cannot parse #{file.path}"
  end
end

#delete(name) ⇒ Object

Deletes an entry



151
152
153
154
155
156
157
158
# File 'lib/safefile.rb', line 151

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

#generate_salt_and_hashObject



192
193
194
195
# File 'lib/safefile.rb', line 192

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

#insert(name, id, pw) ⇒ Object

Inserts an entry



135
136
137
138
# File 'lib/safefile.rb', line 135

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

#list(name) ⇒ Object

Lists the desired entries



161
162
163
164
165
166
167
168
169
170
171
172
# File 'lib/safefile.rb', line 161

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



61
62
63
64
65
66
67
68
69
70
71
# File 'lib/safefile.rb', line 61

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

#saveObject

Saves the file



102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/safefile.rb', line 102

def save
  doc = Document.new
  root = Element.new "safe"
  root.attributes["salt"] = @salt
  root.attributes["hash"] = @hash
  doc.add_element root

  crypt_key = Crypt::Blowfish.new(self.password)
  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, 2
  f.close
end