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)



98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/lenc/repo.rb', line 98

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



290
291
292
293
294
295
296
# File 'lib/lenc/repo.rb', line 290

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



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
224
225
226
227
# File 'lib/lenc/repo.rb', line 158

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



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
142
143
144
145
# File 'lib/lenc/repo.rb', line 117

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



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
284
285
286
287
# File 'lib/lenc/repo.rb', line 240

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.



336
337
338
339
340
341
342
343
344
345
346
347
348
349
# File 'lib/lenc/repo.rb', line 336

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.



306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
# File 'lib/lenc/repo.rb', line 306

def perform_encrypt()
  db = warndb 0
  raise IllegalStateException if @state != STATE_OPEN
  
  enc_dir = @encrDir
  if in_place?
    enc_dir = @repoBaseDir
    !db || pr("perform_encrypt, enc_dir set to repoBaseDir #{@repoBaseDir}\n")
  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
    encrypt_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



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
389
390
391
392
393
394
# File 'lib/lenc/repo.rb', line 362

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