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
53
# File 'lib/safefile.rb', line 47

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.



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

#passwordObject

Returns the value of attribute password.



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

def password
  @password
end

Instance Method Details

#add(name) ⇒ Object

Adds an entry



119
120
121
122
123
124
125
126
127
# File 'lib/safefile.rb', line 119

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)


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

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)


136
137
138
139
140
141
142
143
# File 'lib/safefile.rb', line 136

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



175
176
177
178
179
180
181
182
183
184
185
# File 'lib/safefile.rb', line 175

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



170
171
172
# File 'lib/safefile.rb', line 170

def count()
  @entries.length
end

#create_element(name, text) ⇒ Object

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



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

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

#create_file(file) ⇒ Object

Creates a new file



90
91
92
93
94
# File 'lib/safefile.rb', line 90

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

#decrypt_file(file) ⇒ Object

Decrypts the file



69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/safefile.rb', line 69

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



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

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

#generate_salt_and_hashObject



187
188
189
190
# File 'lib/safefile.rb', line 187

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



130
131
132
133
# File 'lib/safefile.rb', line 130

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

#list(name) ⇒ Object

Lists the desired entries



156
157
158
159
160
161
162
163
164
165
166
167
# File 'lib/safefile.rb', line 156

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



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

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



97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/safefile.rb', line 97

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