lenc 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.txt +2 -0
- data/README.txt +90 -0
- data/bin/lencrypt +5 -0
- data/lib/lenc.rb +1 -0
- data/lib/lenc/aes.rb +523 -0
- data/lib/lenc/config_file.rb +108 -0
- data/lib/lenc/lencrypt.rb +70 -0
- data/lib/lenc/repo.rb +1024 -0
- data/lib/lenc/tools.rb +632 -0
- data/lib/lenc/trollop.rb +782 -0
- data/test/test.rb +377 -0
- metadata +61 -0
data/test/test.rb
ADDED
@@ -0,0 +1,377 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
|
3
|
+
require_relative '../lib/lenc/tools.rb'
|
4
|
+
req('repo lencrypt')
|
5
|
+
|
6
|
+
|
7
|
+
#SINGLETEST = "test_200_recover_with_incorrect_password"
|
8
|
+
if defined? SINGLETEST
|
9
|
+
if main?(__FILE__)
|
10
|
+
ARGV.concat("-n #{SINGLETEST}".split)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class RepoTest < MyTestSuite
|
15
|
+
|
16
|
+
include LEnc
|
17
|
+
|
18
|
+
# Make a directory, if it doesn't already exist
|
19
|
+
def mkdir(name)
|
20
|
+
if !File.directory?(name)
|
21
|
+
Dir::mkdir(name)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def suite_setup
|
26
|
+
# pr("\n\n>>>> RepoTest setup\n\n")
|
27
|
+
|
28
|
+
# Make current directory = the one containing this script
|
29
|
+
main?(__FILE__)
|
30
|
+
|
31
|
+
@@testDir = "__temp_dirs__"
|
32
|
+
mkdir(@@testDir)
|
33
|
+
|
34
|
+
@@sourceDir = File.join(@@testDir,"__source__")
|
35
|
+
@@encryptDir = File.join(@@testDir,"__encrypted__")
|
36
|
+
@@recoverDir = File.join(@@testDir,"__recovered__")
|
37
|
+
@@repoFile = File.join(@@sourceDir,Repo::LENC_REPO_FILENAME)
|
38
|
+
@@key = "onefishtwofishredfishbluefish"
|
39
|
+
@@sampleFile = "demeter"
|
40
|
+
|
41
|
+
if !File.directory?(@@sourceDir)
|
42
|
+
create_source_tree()
|
43
|
+
end
|
44
|
+
|
45
|
+
clean()
|
46
|
+
|
47
|
+
# Construct a list of the source subdirectories, since some of them have
|
48
|
+
# unicode filenames and OSX changes them on us.
|
49
|
+
@@sourceDirs = []
|
50
|
+
getSourceDirList(@@sourceDirs, @@sourceDir)
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
def getSourceDirList(lst, dr)
|
55
|
+
dir_entries(dr).each do |f|
|
56
|
+
child = File.join(dr,f)
|
57
|
+
if File.directory? child
|
58
|
+
lst.push child
|
59
|
+
getSourceDirList(lst, child)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
# Delete .lenc file from source directory,
|
66
|
+
# and delete the encryption and recovery directories
|
67
|
+
def clean
|
68
|
+
# pr("clean, repoFile=%s exists=%s\n",@@repoFile,File.file?(@@repoFile))
|
69
|
+
remove_file_or_dir(@@repoFile)
|
70
|
+
remove_file_or_dir(@@encryptDir)
|
71
|
+
remove_file_or_dir(@@recoverDir)
|
72
|
+
# remove_file_or_dir(bogusSourceFile())
|
73
|
+
end
|
74
|
+
|
75
|
+
|
76
|
+
def suite_teardown
|
77
|
+
# pr("\n\n<<<< RepoTest teardown\n\n")
|
78
|
+
|
79
|
+
if false
|
80
|
+
warn("always removing source dir")
|
81
|
+
|
82
|
+
if @@sourceDir.end_with?("__source__")
|
83
|
+
pr("removing...\n")
|
84
|
+
remove_file_or_dir(@@sourceDir)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def method_setup
|
90
|
+
# pr("\n\n\n")
|
91
|
+
end
|
92
|
+
|
93
|
+
def method_teardown
|
94
|
+
# pr("\n\n\n")
|
95
|
+
end
|
96
|
+
|
97
|
+
|
98
|
+
|
99
|
+
def makeFile(pth, lngth=3)
|
100
|
+
remove_file_or_dir(pth)
|
101
|
+
|
102
|
+
srand(1965)
|
103
|
+
|
104
|
+
File.open(pth, 'wb') do |f|
|
105
|
+
j = 0
|
106
|
+
while j < lngth
|
107
|
+
if (j+1) % 40 == 0
|
108
|
+
f.write("\n")
|
109
|
+
else
|
110
|
+
f.write((rand(26)+65).chr)
|
111
|
+
end
|
112
|
+
j += 1
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def unicode(c)
|
118
|
+
s = [c].pack("U*")
|
119
|
+
s
|
120
|
+
end
|
121
|
+
|
122
|
+
def create_source_tree
|
123
|
+
# Create a set of directories and files within the repo's source directory.
|
124
|
+
|
125
|
+
# Specify a script as a sequence of names, each followed by
|
126
|
+
# | : file
|
127
|
+
# / : directory
|
128
|
+
# ^ : parent directory (name should be empty)
|
129
|
+
|
130
|
+
# Some names have unicode characters for test purposes.
|
131
|
+
|
132
|
+
scr =<<SCR
|
133
|
+
|
134
|
+
mars| sol| luna| aphrodite | apollo | ares | artemis | hades | demeter |
|
135
|
+
alpha/
|
136
|
+
earth| venus| symphony 1 | symphony 2 | hephaestus | hera | hermes | hestia |
|
137
|
+
delt#{unicode(0xe5)} /
|
138
|
+
jupiter| saturn| poseidon | zeus |
|
139
|
+
^
|
140
|
+
^
|
141
|
+
bet#{unicode(0xe1)} gamma|
|
142
|
+
uranus|
|
143
|
+
neptune|
|
144
|
+
pluto|
|
145
|
+
SCR
|
146
|
+
|
147
|
+
# Script of predetermined file lengths:
|
148
|
+
cs = RepoInternal::CHUNK_SIZE_ENCR
|
149
|
+
hs = RepoInternal::CHUNK_HEADER_SIZE
|
150
|
+
flens = [0, cs - 1, cs, cs + 1, cs + hs - 1, cs + hs, cs + hs + 1,
|
151
|
+
(2 * cs) - 1, 2 * cs, 2 * cs + 1, 2 * (cs + hs) - 1, 2 * (cs + hs), 2 * (cs + hs) + 1]
|
152
|
+
fNum = 0
|
153
|
+
|
154
|
+
srand(1983)
|
155
|
+
|
156
|
+
dr = @@sourceDir
|
157
|
+
mkdir(dr)
|
158
|
+
|
159
|
+
dirStack = []
|
160
|
+
k = 0
|
161
|
+
k0 = k
|
162
|
+
while k < scr.size
|
163
|
+
c = scr[k]
|
164
|
+
k += 1
|
165
|
+
next if !"|/^".index(c)
|
166
|
+
|
167
|
+
nm = scr[k0...k-1].strip
|
168
|
+
|
169
|
+
k0 = k
|
170
|
+
|
171
|
+
if c == '|'
|
172
|
+
# create a file; use our predetermined lengths, if we still have some;
|
173
|
+
# otherwise, choose a random file length
|
174
|
+
if fNum < flens.size
|
175
|
+
j = flens[fNum]
|
176
|
+
else
|
177
|
+
j = (rand() * rand() * 130000).to_i + 3
|
178
|
+
end
|
179
|
+
fNum += 1
|
180
|
+
makeFile(File.join(dr, nm), j)
|
181
|
+
elsif c == '/'
|
182
|
+
dirStack.push(dr)
|
183
|
+
dr = File.join(dr,nm)
|
184
|
+
mkdir(dr)
|
185
|
+
elsif c == '^'
|
186
|
+
dr = dirStack.pop
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
# Store some .lencignore files
|
191
|
+
dr = @@sourceDir
|
192
|
+
writeIgnore(dr, "s*")
|
193
|
+
|
194
|
+
dr = File.join(dr, "alpha")
|
195
|
+
writeIgnore(dr, "hera")
|
196
|
+
|
197
|
+
dr = File.join(dr, "delt#{unicode(0xe5)}")
|
198
|
+
writeIgnore(dr, "!s*")
|
199
|
+
end
|
200
|
+
|
201
|
+
|
202
|
+
def writeIgnore(dr, contents)
|
203
|
+
pth = File.join(dr,Repo::IGNOREFILENAME)
|
204
|
+
File.open(pth,'w') do |f|
|
205
|
+
f.write("# This is a .lencignore file\n#{contents}\n\n### end ###\n")
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
def ex(args)
|
210
|
+
if args.is_a? String
|
211
|
+
args = args.split
|
212
|
+
end
|
213
|
+
args.concat(["-w", @@sourceDir, "-q"])
|
214
|
+
LEncApp.new().run(args)
|
215
|
+
end
|
216
|
+
|
217
|
+
# def bogusSourceFile
|
218
|
+
# return File.join(@@sourceDir, "_#bogusfile.txt")
|
219
|
+
# end
|
220
|
+
|
221
|
+
def build_repo_obj(silent = true)
|
222
|
+
v = silent ? -1 : 0
|
223
|
+
rp = Repo.new(:verbosity => v)
|
224
|
+
rp
|
225
|
+
end
|
226
|
+
|
227
|
+
def create_repo
|
228
|
+
clean
|
229
|
+
rp = build_repo_obj
|
230
|
+
rp.create(@@sourceDir,@@key,@@encryptDir)
|
231
|
+
rp.close
|
232
|
+
rp
|
233
|
+
end
|
234
|
+
|
235
|
+
def update_repo(silent = true)
|
236
|
+
rp = build_repo_obj(silent)
|
237
|
+
rp.open(@@sourceDir)
|
238
|
+
rp.perform_update
|
239
|
+
rp.close
|
240
|
+
end
|
241
|
+
|
242
|
+
def do_recover
|
243
|
+
rp = build_repo_obj
|
244
|
+
rp.perform_recovery(@@key, @@encryptDir,@@recoverDir)
|
245
|
+
rp.close
|
246
|
+
end
|
247
|
+
|
248
|
+
# --------------- tests --------------------------
|
249
|
+
|
250
|
+
|
251
|
+
|
252
|
+
def test_050_AES_encryption
|
253
|
+
req 'aes'
|
254
|
+
|
255
|
+
f = RepoInternal::MyAES.new(true, @@key, "42")
|
256
|
+
|
257
|
+
originalText = ""
|
258
|
+
while originalText.size < 100000
|
259
|
+
64.times do |i|
|
260
|
+
ch = ((i % 26) + 65).chr
|
261
|
+
originalText << ch * 17
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
enc = ""
|
266
|
+
s = originalText.size
|
267
|
+
n = 0
|
268
|
+
while n < s
|
269
|
+
c = [60,s-n].min
|
270
|
+
f.add(originalText[n...n+c])
|
271
|
+
n += c
|
272
|
+
enc << f.flush()
|
273
|
+
end
|
274
|
+
|
275
|
+
f.finish()
|
276
|
+
enc << f.flush
|
277
|
+
|
278
|
+
require 'digest/md5'
|
279
|
+
|
280
|
+
digest = Digest::MD5.hexdigest(enc)
|
281
|
+
assert(digest.to_s == '284740fbf3355951e9e76fb43fda985c')
|
282
|
+
|
283
|
+
f = RepoInternal::MyAES.new(false, @@key)
|
284
|
+
dec = ""
|
285
|
+
n = 0
|
286
|
+
s = enc.size
|
287
|
+
while n < s
|
288
|
+
c = [60,s-n].min
|
289
|
+
f.add(enc[n...n+c])
|
290
|
+
n += c
|
291
|
+
dec << f.flush
|
292
|
+
end
|
293
|
+
|
294
|
+
f.finish()
|
295
|
+
dec << f.flush
|
296
|
+
assert(originalText == dec)
|
297
|
+
end
|
298
|
+
|
299
|
+
|
300
|
+
# Create repo using Repo class
|
301
|
+
def test_100_create_repo
|
302
|
+
create_repo
|
303
|
+
assert(File.file?(@@repoFile))
|
304
|
+
end
|
305
|
+
|
306
|
+
# Create repo using lencrypt program
|
307
|
+
def test_101_create_repo
|
308
|
+
clean
|
309
|
+
a = "-i #{@@key} #{@@encryptDir}"
|
310
|
+
ex(a)
|
311
|
+
end
|
312
|
+
|
313
|
+
def test_101_update_repo_with_verify
|
314
|
+
clean
|
315
|
+
ex("-i #{@@key} #{@@encryptDir}")
|
316
|
+
rp = build_repo_obj
|
317
|
+
rp.open(@@sourceDir)
|
318
|
+
rp.perform_update(true)
|
319
|
+
rp.close
|
320
|
+
end
|
321
|
+
|
322
|
+
def test_110_open_repo_using_Repo_class
|
323
|
+
# (assumes repo exists at this point; preceding test creates it)
|
324
|
+
rp = build_repo_obj
|
325
|
+
rp.open(@@sourceDir)
|
326
|
+
end
|
327
|
+
|
328
|
+
def test_120_update_repo_using_Repo_class
|
329
|
+
update_repo
|
330
|
+
assert(File.directory?(@@encryptDir))
|
331
|
+
end
|
332
|
+
|
333
|
+
def test_121_update_repo_with_lencrypt_program
|
334
|
+
remove_file_or_dir(@@encryptDir)
|
335
|
+
ex("")
|
336
|
+
assert(File.directory?(@@encryptDir))
|
337
|
+
end
|
338
|
+
|
339
|
+
def test_125_recover_repository
|
340
|
+
remove_file_or_dir(@@recoverDir)
|
341
|
+
do_recover
|
342
|
+
assert(File.directory?(@@recoverDir))
|
343
|
+
end
|
344
|
+
|
345
|
+
def test_130_update_when_repository_not_found
|
346
|
+
assert_raise(RepoNotFoundException) do
|
347
|
+
clean
|
348
|
+
rp = build_repo_obj
|
349
|
+
rp.open(@@sourceDir)
|
350
|
+
end
|
351
|
+
end
|
352
|
+
|
353
|
+
def test_200_recover_with_incorrect_password
|
354
|
+
create_repo
|
355
|
+
ex("") # updates repo
|
356
|
+
assert_raise(DecryptionError) do
|
357
|
+
remove_file_or_dir(@@recoverDir)
|
358
|
+
rp = build_repo_obj()
|
359
|
+
rp.perform_recovery("thisisthewrongkey_______", @@encryptDir,@@recoverDir)
|
360
|
+
rp.close
|
361
|
+
end
|
362
|
+
end
|
363
|
+
|
364
|
+
# def test_300_source_contains_encrypted_filename
|
365
|
+
# create_repo
|
366
|
+
# update_repo
|
367
|
+
#
|
368
|
+
# makeFile(bogusSourceFile())
|
369
|
+
# assert_raise(UpdateException) do
|
370
|
+
# update_repo
|
371
|
+
# end
|
372
|
+
# remove_file_or_dir(bogusSourceFile())
|
373
|
+
# end
|
374
|
+
|
375
|
+
|
376
|
+
|
377
|
+
end
|
metadata
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: lenc
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jeff Sember
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-03-19 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: "Encrypts a set of local files, and copies the encrypted versions to
|
14
|
+
a repository, \nwhich may be located within a free cloud service (Dropbox, Google
|
15
|
+
Drive, Microsoft SkyDrive). \nThe program uses the trusted AES 256-bit encryption
|
16
|
+
standard. \nAll files are encrypted on the user machine; \npasswords and unencrypted
|
17
|
+
files are never seen by the cloud service. \n"
|
18
|
+
email: jpsember@gmail.com
|
19
|
+
executables:
|
20
|
+
- lencrypt
|
21
|
+
extensions: []
|
22
|
+
extra_rdoc_files: []
|
23
|
+
files:
|
24
|
+
- lib/lenc.rb
|
25
|
+
- lib/lenc/aes.rb
|
26
|
+
- lib/lenc/config_file.rb
|
27
|
+
- lib/lenc/lencrypt.rb
|
28
|
+
- lib/lenc/repo.rb
|
29
|
+
- lib/lenc/tools.rb
|
30
|
+
- lib/lenc/trollop.rb
|
31
|
+
- bin/lencrypt
|
32
|
+
- CHANGELOG.txt
|
33
|
+
- README.txt
|
34
|
+
- test/test.rb
|
35
|
+
homepage: http://www.cs.ubc.ca/~jpsember/
|
36
|
+
licenses:
|
37
|
+
- mit
|
38
|
+
metadata: {}
|
39
|
+
post_install_message:
|
40
|
+
rdoc_options: []
|
41
|
+
require_paths:
|
42
|
+
- lib
|
43
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
49
|
+
requirements:
|
50
|
+
- - '>='
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: '0'
|
53
|
+
requirements: []
|
54
|
+
rubyforge_project:
|
55
|
+
rubygems_version: 2.0.2
|
56
|
+
signing_key:
|
57
|
+
specification_version: 4
|
58
|
+
summary: Maintains an encrypted repository of a set of files for secure cloud storage.
|
59
|
+
test_files:
|
60
|
+
- test/test.rb
|
61
|
+
has_rdoc:
|