Class: LEnc::Repo

Inherits:
Object
  • Object
show all
Includes:
RepoInternal
Defined in:
lib/lenc/repo.rb

Overview

Represents an encrypted repository

Constant Summary collapse

LENC_REPO_FILENAME =
".lenc"
ENCRFILENAMEPREFIX =
"_#"

Constants included from RepoInternal

RepoInternal::AES_BLOCK_SIZE, RepoInternal::CHUNK_HEADER_SIZE, RepoInternal::CHUNK_SIZE_DECR, RepoInternal::CHUNK_SIZE_ENCR, RepoInternal::CHUNK_VERIFY_SIZE, RepoInternal::CHUNK_VERIFY_STR, RepoInternal::NONCE_SIZE_LARGE, RepoInternal::NONCE_SIZE_SMALL, RepoInternal::PAD_BYTE, RepoInternal::PAD_CHAR

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ Repo

Construct a repository object. It is created in a ‘closed’ state, in that it is not associated with a particular repository in the file system.

:dryrun if true, no files on the filesystem will be affected by this
    object throughout its lifetime.  Useful for showing the user what
    would happen if dryrun were false.
:verbosity controls the amount of feedback during this object's lifetime.
     default 0; if < 0, silent; if > 0, talkative

Parameters:

  • options (defaults to: {})

    hash table of optional parameters; e.g. r = LEnc::Repo(:verbosity => 2, strict => True)



94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# File 'lib/lenc/repo.rb', line 94

def initialize(options = {})
 
  reset_state()
  
  @dryrun = options.delete :dryrun
  @verbosity = (options.delete :verbosity) || 0
  
  # During recovery, we will use the first file we encounter to 
  # verify if the supplied password is correct, and abort if not.
  @recovery_pwd_verified = false
  
  if options.size > 0
    raise ArgumentError, "Unrecognized options: " + d2(options)
  end
end

Instance Method Details

#closeObject

Close the repository, if it is open



286
287
288
289
290
291
292
# File 'lib/lenc/repo.rb', line 286

def close 
  return if @state == STATE_CLOSED
  
  raise IllegalStateException if @state != STATE_OPEN
  
  reset_state()
end

#create(repo_dir, key, enc_dir, original_names = false) ⇒ Object

Create a new encryption repository, and open it.

if nil, repository will be encrypted in-place

Parameters:

  • repo_dir

    directory of new repository (nil for current directory)

  • key

    encryption key, a string from KEY_LEN_MIN to KEY_LEN_MAX characters in length

  • enc_dir

    if not nil, directory to store encrypted files; must not yet exist, and must not represent a directory lying within the repo_dir tree;

  • original_names (defaults to: false)

    if true, the filenames are not encrypted, only the file contents

Raises:

  • ArgumentError if appropriate



154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
# File 'lib/lenc/repo.rb', line 154

def create(repo_dir, key, enc_dir, original_names=false) 
  raise IllegalStateException if @state != STATE_CLOSED 
  
  db = warndb 0
  !db || pr("Repo.create, %s\n",da( [repo_dir,key,enc_dir,original_names]))
  repo_dir ||= Dir.pwd

  if  !File.directory?(repo_dir)
    raise ArgumentError, "Not a directory: #{repo_dir}"
  end
  
  # Verify that there is no repository. 
  # Construct a ConfigFile object to determine if it already exists
  @confFile = ConfigFile.new(LENC_REPO_FILENAME, repo_dir)
  
  if @confFile.exists()
    raise ArgumentError, 'Encryption repository already exists: ' \
     + @confFile.path
  end
  
  @confFile.set('version', @version)
  
  @orignames = original_names
  
  @confFile.set('orignames', @orignames)
  
  if @verbosity >= 1 
      pr("Creating encryption repository %s\n", @confFile.path)
  end
  
  edir = nil
  if enc_dir
    edir = File.absolute_path(enc_dir)
    pp = verifyDirsDistinct([repo_dir, edir])

    if pp
      raise ArgumentError, "Directory " + pp[0] + \
      " is a subdirectory of " + pp[1]
    end
    
    if File.exists?(edir)
      raise ArgumentError, \
      "Encryption directory or file already exists: '#{edir}'"
    end
    @confFile.set('enc_dir', edir)
  end

  key = define_password(key)
  if (key.size < KEY_LEN_MIN || key.size > KEY_LEN_MAX) 
    raise ArgumentError, "Password length " + key.size.to_s \
      + " is illegal" 
  end
  
  # Construct a string that verifies the password is correct
  en = MyAES.new(true, key  )
  en.finish("!!!")            
  verifier_string = en.flush

  # Store key verifier as an array of bytes, to avoid nonprintable problems
  vs2 = Base64.urlsafe_encode64(verifier_string)
  @confFile.set('key_verifier', vs2)
  
  if not @dryrun 
    if edir
      Dir.mkdir(edir)
    end
    @confFile.write()
  end
  
end

#define_password(pwd) ⇒ Object

If a password hasn’t been defined, ask user for one. Also, pad password out to some minimum size



113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/lenc/repo.rb', line 113

def define_password(pwd)
  if !pwd
    
    if true
      
      # Use the 'highline' gem to allow typing password without echo to screen
      require 'rubygems'
      require 'highline/import'
      pwd = ask("Password: ") {|q| q.echo = false}
        
    else
      printf("Password: ")
      pwd = gets
    end
    
    if pwd
      pwd.strip!
      pwd = nil if pwd.size == 0
    end
    if !pwd
      raise DecryptionError, "No password given"
    end
  end
  
  while pwd.size < KEY_LEN_MIN
    pwd *= 2
  end
  pwd
end

#open(startDirectory = nil, password = nil) ⇒ Object

Open the repository, by associating it with one in the file system.

Parameters:

  • startDirectory (defaults to: nil)

    directory lying within repository tree; if nil, uses current directory

  • password (defaults to: nil)

    repository password; if a password was stored with the repository when it was created, this parameter is ignored; if this parameter is null, and no password was stored with the repository, the user will be prompted for one

Raises:

  • IllegalStateException if repository is already open

  • ArgumentError if directory doesn’t exist, or does not lie in a repository



236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
# File 'lib/lenc/repo.rb', line 236

def open(startDirectory = nil, password = nil) 
  db = warndb 0
  !db || pr("Repo.open startDir=%s, password=%s\n",d(startDirectory),d(password))
    
  raise IllegalStateException if @state != STATE_CLOSED 
    
  startDirectory ||= Dir.pwd
  
  if not File.directory?(startDirectory)
      raise ArgumentError,"Not a directory: '" + startDirectory + "'"
  end

  cfile = Repo.findRepository(startDirectory)
  !db || pr(" find repo (%s) => %s\n",d(startDirectory),d(cfile))
    
  if !cfile 
    raise RepoNotFoundException, "Can't find repository"
  end
  
  @confFile = cfile
  @startDir = startDirectory
  @repoBaseDir = cfile.get_directory 
  
  cfVersion = @confFile.val('version', 0)
  if cfVersion > @version 
    raise(VersionError,"Repository was built with a more recent version of the program.")
  end
  
  if cfVersion.floor < @version.floor
    raise(VersionError,"Repository was built with an older version; rebuild it")
  end
  
  # Read values from configuration to instance vars
  @encrDir = @confFile.val('enc_dir')
  pwd = define_password(password)
  
  @orignames = @confFile.val('orignames')
  @encrKey = pwd
  
  prepareKeys()
  
  key_verifier = @confFile.val('key_verifier')
  key_verifier = Base64.urlsafe_decode64(key_verifier)
        
  verify_encrypt_pwd(pwd, key_verifier)
  
  @state = STATE_OPEN
end

#perform_decryptObject

Decrypt files within singular repository. Repository must be open.

Raises:

  • IllegalStateException if repository isn’t open.



330
331
332
333
334
335
336
337
338
339
340
341
342
343
# File 'lib/lenc/repo.rb', line 330

def perform_decrypt()
  raise IllegalStateException if (@state != STATE_OPEN || !in_place?)

  enc_dir = @repoBaseDir

  setInputOutputDirs(enc_dir,enc_dir)

  puts("Decrypting...") if @verbosity >= 1

  begin
    decrypt_directory_contents(enc_dir)
    puts("...done.") if @verbosity >= 1
  end
end

#perform_encryptObject

Encrypt repository’s files. If repo is dual, finds files that need to be re-encrypted and does so. If singular, encrypts all those files that are not yet encrypted.

Repository must be open.

Raises:

  • IllegalStateException if repository isn’t open.



302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
# File 'lib/lenc/repo.rb', line 302

def perform_encrypt()
  raise IllegalStateException if @state != STATE_OPEN
  
  enc_dir = @encrDir
  if in_place?
    enc_dir = @repoBaseDir
  end
  
  setInputOutputDirs(@startDir,enc_dir)
  
  # If encrypting singular repository, ignore all .lencignore files
  if in_place?
    pushIgnoreList('', Repo.parseIgnoreList(".lencignore"))
  end
  
  puts("Encrypting...") if @verbosity >= 1

  begin
    ecrypt_directory_contents(@repoBaseDir, enc_dir)
    puts("...done.") if @verbosity >= 1
  end
end

#perform_recovery(key, eDir, rDir) ⇒ Object

Recover files from a repository’s encryption folder.

Parameters:

  • key

    encryption key

  • eDir

    encryption directory

  • rDir

    directory to write decrypted files to; creates it if necessary. Must not lie within eDir tree.

Raises:

  • ArgumentError if problem with the directory arguments;

  • DecryptionError if incorrect password provided, and strict mode in effect



356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
# File 'lib/lenc/repo.rb', line 356

def perform_recovery(key, eDir, rDir) 
  raise IllegalStateException  if @state != STATE_CLOSED
  
  ret = nil
  
  key = define_password(key)
  @encrKey = key
  
  rd = File.absolute_path(rDir)
  prepareKeys()
  
  if not File.directory?(eDir)
    raise ArgumentError, "Not a directory: '" + eDir + "'" 
  end
  
  # There must not exist a repository in the recovery directory
  cf = Repo.findRepository(rd)
  
  if cf 
    raise ArgumentError, "Recovery directory lies within repository: " + cf.getPath()
  end
  
  puts("Recovering...") if @verbosity >= 1
  
  setInputOutputDirs(eDir,rd)

  begin
    recover(eDir, rd)
    print("...done.") if @verbosity >= 1 
  end
    
  ret
end