ampk 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 16ec52ea4400c790a5d30a3855ba5822fe8f9eea
4
+ data.tar.gz: 5cddbce6840ae250d4ee25f9546d4f04c1e52d44
5
+ SHA512:
6
+ metadata.gz: 21e2b879cadf32f9b6321b87954442ea1d19a3296320937e573becb677e88943290d8397f72f7014fa8897fb3b812f57b9d417178b5b2fea0aaae11f9c47d826
7
+ data.tar.gz: 2035dcc012105fa13b24b923a3b2d98c867bdb2ddc6aad1742375c3078ff4918e4b6289fed2ca6d1e11c56bdfff89ab73a3bb21bf54ffa979debb7cd6ada2500
@@ -0,0 +1,94 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ lib = File.expand_path('../../lib/', __FILE__)
4
+ $:.unshift lib unless $:.include?(lib)
5
+
6
+ require 'ampk'
7
+ require 'optparse'
8
+ Version = [0,0,1]
9
+ def Version.to_s; "v#{join('.')}"; end
10
+
11
+ options = {
12
+ :verbose => false
13
+ }
14
+
15
+ parser = OptionParser.new do |opts|
16
+ opts.banner = "Usage: ampk [options] <archive> [<files>]"
17
+ opts.on('-v', '--verbose', 'Verbose output') do
18
+ options[:verbose] = true
19
+ end
20
+ opts.on('-V', '--version', 'Display version') do
21
+ options[:mode] = :version
22
+ end
23
+ opts.on('-k', '--key=KEYFILE', 'Keyfile') do |key|
24
+ options[:keyfile] = key
25
+ end
26
+ opts.on('-d', '--digest=DIGEST', 'Digest/Password for keyfile') do |digest|
27
+ options[:digest] = digest
28
+ end
29
+ opts.on('-t', '--test', 'Check archive') do |v|
30
+ options[:mode] = :test
31
+ end
32
+ opts.on('-x', '--extract', 'Extract archive contents') do
33
+ options[:mode] = :extract
34
+ end
35
+ opts.on('-c', '--create', 'Create an archive') do
36
+ options[:mode] = :create
37
+ end
38
+ opts.on('-l', '--list', 'List archive contents') do
39
+ options[:mode] = :list
40
+ end
41
+ end
42
+ parser.program_name = File.basename(__FILE__)
43
+ parser.parse!
44
+
45
+ case options[:mode]
46
+ when :create
47
+ key = Crypto::Key.from_file(options[:keyfile], options[:digest]) if options[:keyfile]
48
+ archive = AmpkReader.new(ARGV[0], key)
49
+ if archive.signed_or_encrypted?
50
+ if key.nil? && !options[:nocrypt]
51
+ raise "Archive requires a valid keyfile to verify"
52
+ end
53
+ end
54
+
55
+ archive.verify!
56
+ when :extract
57
+ when :list
58
+ key = Crypto::Key.from_file(options[:keyfile], options[:digest]) if options[:keyfile]
59
+ archive = AmpkReader.new(ARGV[0], key)
60
+
61
+ bits = [['Type', 'Flags', 'Size', 'Filename']]
62
+
63
+ archive.entities.each do |name|
64
+ info = archive.entity(name)
65
+ flags = info[:filter].to_s
66
+ flags += "S" if info[:signature]
67
+ bits << [info[:type], flags, info[:length], name]
68
+ end
69
+
70
+ if options[:verbose]
71
+ col_size = [0]
72
+ bits[0].each_with_index do |item, index|
73
+ col_size[index] = 0
74
+ bits.each do |row|
75
+ col_size[index] = [col_size[index], row[index].to_s.size].max
76
+ end
77
+ end
78
+ fmt = col_size.inject('') do |fmt, size|
79
+ fmt << " | " unless fmt.empty?
80
+ fmt << "%#{size}s"
81
+ end << "\n"
82
+ bits.each_with_index do |bit, index|
83
+ printf fmt, *bit
84
+ printf fmt, *col_size.map { |i| '-' * i } if index.zero?
85
+ end
86
+ else
87
+ puts archive.entities.join("\n")
88
+ end
89
+ when :version
90
+ puts parser.version.to_s
91
+ else
92
+ puts parser.help
93
+ end
94
+
@@ -0,0 +1,6 @@
1
+ require 'zlib'
2
+ require 'ampk/writer'
3
+ require 'ampk/reader'
4
+ require 'ampk/crypto'
5
+ require 'ampk/version'
6
+
@@ -0,0 +1,54 @@
1
+ require 'fileutils'
2
+ require 'openssl'
3
+ require 'base64'
4
+
5
+ module Crypto
6
+ def self.create_keys(priv = "dsa_key", pub = "#{priv}", bits = 1024)
7
+ private_key = OpenSSL::PKey::RSA.new(bits)
8
+ FileUtils.mkdir_p('private')
9
+ FileUtils.mkdir_p('public')
10
+ File.open("private/"+priv+".private", "w+") { |fp| fp << private_key.to_s }
11
+ File.open("public/"+pub+".public", "w+") { |fp| fp << private_key.public_key.to_s }
12
+ private_key
13
+ end
14
+
15
+ class Key
16
+ attr_accessor :digest_string
17
+ def initialize(data, digest_string)
18
+ @public = (data =~ /^-----BEGIN (RSA|DSA) PRIVATE KEY-----$/).nil?
19
+ @prefix = @public ? "public" : "private"
20
+ @key = OpenSSL::PKey::RSA.new(data)
21
+ @digest_string = digest_string
22
+ end
23
+
24
+ def self.from_file(filename, digest_string)
25
+ self.new(File.read(filename), digest_string)
26
+ end
27
+
28
+ def encrypt(text)
29
+ Base64.encode64(@key.send("#{@prefix}_encrypt", text))
30
+ end
31
+
32
+ def decrypt(text)
33
+ @key.send("#{@prefix}_decrypt", Base64.decode64(text))
34
+ end
35
+
36
+ def sign(data)
37
+ Base64.encode64(@key.sign(digest, data))
38
+ end
39
+
40
+ def verify(signature, data)
41
+ @key.verify(digest, Base64.decode64(signature), data)
42
+ end
43
+
44
+ def public?
45
+ @public
46
+ end
47
+
48
+ def digest
49
+ OpenSSL::Digest::MD5.new(@digest_string)
50
+ end
51
+ end
52
+ end
53
+
54
+
@@ -0,0 +1,232 @@
1
+ require 'zlib'
2
+
3
+ class AmpkReader
4
+ attr_reader :items, :path
5
+ def initialize(path, public_key)
6
+ @fp = File.open(@path=path,'rb')
7
+ @public_key = public_key
8
+ end
9
+ def verify!(verify_data=false)
10
+ @fp.rewind
11
+ must('start AMPK') { read4 == 'AMPK' }
12
+ until @fp.eof?
13
+ break if peek4 == 'ENDS'
14
+ name = verify_entry('NAME', true)
15
+ headers = {}
16
+ signature = nil
17
+
18
+ until (header = peek4).eql?('DATA')
19
+ headers[header] ||= 0
20
+ headers[header] += 1
21
+ raise "Duplicate #{header} entry for #{name}" if headers[header] > 1
22
+
23
+ case header
24
+ when 'FILT', 'DLEN', 'TYPE'
25
+ verify_entry(header, false)
26
+ when 'SIGN'
27
+ signature = verify_entry('SIGN', true)
28
+ end
29
+ end
30
+
31
+ if signature
32
+ verify_entry_with_sig('DATA', verify_data, signature)
33
+ else
34
+ verify_entry('DATA', verify_data)
35
+ end
36
+ end
37
+ end
38
+ def entities
39
+ auto_scan
40
+ @items.map { |item| item[:name] }
41
+ end
42
+ def entity_signed?(name)
43
+ entity(name).has_key?(:signature)
44
+ end
45
+ def entity_encrypted?(name)
46
+ item = entity(name)
47
+ item.has_key?(:filter) and item[:filter].include?(?C)
48
+ end
49
+ def entity_compressed?(name)
50
+ item = entity(name)
51
+ item.has_key?(:filter) and item[:filter].include?(?Z)
52
+ end
53
+ def entity_size(name)
54
+ entity(name)[:length]
55
+ end
56
+ alias :entity_length :entity_size
57
+ def entity_type(name)
58
+ entity(name)[:type]
59
+ end
60
+ def entity(name)
61
+ auto_scan
62
+ item = @items.find { |item| item[:name] == name }
63
+ raise Errno::ENOENT, name if item.nil?
64
+ item
65
+ end
66
+ def read_entity(name)
67
+ auto_scan unless @items
68
+ item = @items.find { |item| item[:name] == name }
69
+ raise Errno::ENOENT, name if item.nil?
70
+ read_entity_data(item)
71
+ end
72
+ def close
73
+ @fp.close
74
+ end
75
+
76
+ protected
77
+ def auto_scan
78
+ read_headers unless @items
79
+ nil
80
+ end
81
+
82
+ def read_headers
83
+ @fp.rewind
84
+ must('start AMPK') { read4 == 'AMPK' }
85
+ @items = []
86
+ until @fp.eof?
87
+ break if peek4 == 'ENDS'
88
+ @items << read_entity_header()
89
+ skip_entry('DATA')
90
+ end
91
+ @items
92
+ end
93
+
94
+ def read_entity_header
95
+ start = @fp.tell
96
+ name = read_entry('NAME')
97
+ item = { :name => name, :header => start }
98
+ until (header=peek4).eql?('DATA')
99
+ case header
100
+ when 'SIGN'
101
+ item[:signature] = read_entry(header)
102
+ when 'DLEN'
103
+ item[:length] = read_entry(header).to_i
104
+ when 'FILT'
105
+ item[:filter] = read_entry(header)
106
+ when 'TYPE'
107
+ item[:type] = read_entry(header)
108
+ when 'ENCD'
109
+ item[:encoding] = read_entry(header)
110
+ else
111
+ raise RuntimeError, "Unknown/invalid header #{header}"
112
+ end
113
+ end
114
+ item[:offset] = @fp.tell
115
+ item
116
+ end
117
+
118
+ def read_entity_data(item)
119
+ @fp.seek(item[:offset])
120
+ data = read_entry('DATA')
121
+
122
+ if item[:filter]
123
+ item[:filter].reverse.each_byte do |byte|
124
+ case byte
125
+ when ?Z.ord
126
+ data = Zlib::Inflate.inflate(data)
127
+ when ?C.ord
128
+ data = @public_key.decrypt(data)
129
+ end
130
+ end
131
+ end
132
+
133
+ if item[:signature]
134
+ if @public_key.verify(item[:signature], data)
135
+ return decode(item, data)
136
+ else
137
+ raise RuntimeError, "Invalid data for #{item[:name]}"
138
+ end
139
+ else
140
+ return decode(item, data)
141
+ end
142
+ end
143
+
144
+ def decode(item, data)
145
+ if item[:encoding] && data.respond_to?(:encoding)
146
+ data.force_encoding(item[:encoding])
147
+ else
148
+ data
149
+ end
150
+ end
151
+
152
+ def read_entry(name)
153
+ must("have a #{name} entry") { read4 == name }
154
+ size, data = read_data
155
+ must("have some #{name} data") { size == data.size }
156
+ data
157
+ end
158
+
159
+ def skip_entry(name)
160
+ must("have a #{name} entry'") { read4 == name }
161
+ size, skipped = skip_data
162
+ must("have some #{name} data") { size == skipped }
163
+ nil
164
+ end
165
+
166
+ def verify_entry(name, verify_data)
167
+ must("have a #{name} entry") { read4 == name }
168
+ if verify_data
169
+ size, data = read_data
170
+ must("have some #{name} data") { size == data.size }
171
+ data
172
+ else
173
+ size, skipped = skip_data
174
+ must("have at least as much data left") { skipped == size }
175
+ nil
176
+ end
177
+ end
178
+
179
+ def verify_entry_with_sig(name, verify_data, sig)
180
+ data = verify_entry(name, verify_data)
181
+ if data and sig
182
+ must('be valid, signed data') { @public_key.verify(sig, data) }
183
+ end
184
+ end
185
+
186
+ def must(description,&block)
187
+ raise RuntimeError, "Archive is invalid: It must #{description}" unless yield
188
+ end
189
+
190
+ def read4
191
+ str = @fp.read(4)
192
+ str
193
+ end
194
+
195
+ def peek4
196
+ pos = @fp.tell
197
+ str = @fp.read(4)
198
+ @fp.seek(pos, IO::SEEK_SET)
199
+ str
200
+ end
201
+
202
+ def read_data_size
203
+ size = ""
204
+ while (c = get_byte).nonzero?
205
+ size << c
206
+ end
207
+ size.to_i
208
+ end
209
+
210
+ def get_byte
211
+ if @fp.respond_to?(:getbyte)
212
+ @fp.getbyte
213
+ else
214
+ @fp.getc
215
+ end
216
+ end
217
+
218
+ def read_data
219
+ size = read_data_size
220
+ data = @fp.read(size)
221
+ [size, data]
222
+ end
223
+
224
+ def skip_data
225
+ size = read_data_size
226
+ posn = @fp.tell
227
+ @fp.seek(size, IO::SEEK_CUR)
228
+ skipped = @fp.tell - posn
229
+ [size, skipped]
230
+ end
231
+ end
232
+
@@ -0,0 +1,6 @@
1
+ module Ampk
2
+ VERSION = [0,0,5]
3
+ def VERSION.to_s
4
+ self * "."
5
+ end
6
+ end
@@ -0,0 +1,69 @@
1
+ require 'zlib'
2
+
3
+ class AmpkWriter
4
+ def initialize(path, private_key, &block)
5
+ @fp = File.open(path,'wb')
6
+ write("AMPK")
7
+ @private_key = private_key
8
+ if block_given?
9
+ begin
10
+ yield(self)
11
+ ensure
12
+ close()
13
+ end
14
+ end
15
+ end
16
+ def add_entity(name, data, options = {})
17
+ write_entry("NAME", name)
18
+ write_entry("DLEN", size(data).to_s)
19
+
20
+ if data.respond_to?(:encoding)
21
+ write_entry("ENCD", data.encoding.name)
22
+ data = data.force_encoding("ASCII-8BIT")
23
+ end
24
+
25
+ write_entry("TYPE", options[:type] || 'application/octet-stream')
26
+ if options[:sign] || options[:signature]
27
+ write_entry("SIGN", options[:signature] || @private_key.sign(data))
28
+ end
29
+ filter = ""
30
+ if options[:encrypt] || options[:crypt]
31
+ filter << "C"
32
+ data = @private_key.encrypt(data)
33
+ end
34
+ orig_size = size(data)
35
+ if options[:compress] and size(data) > 256
36
+ filter << "Z"
37
+ data = Zlib::Deflate.deflate(data)
38
+ end
39
+ unless filter.empty?
40
+ write_entry('FILT', filter)
41
+ end
42
+ write_size = size(data)
43
+ write_entry("DATA", data)
44
+ end
45
+
46
+ def close
47
+ write('ENDS')
48
+ @fp.close()
49
+ end
50
+
51
+ protected
52
+ def size(data)
53
+ sz = data.size
54
+ sz = data.bytesize if data.respond_to?(:bytesize)
55
+ sz
56
+ end
57
+ def write(str)
58
+ @fp.write(str)
59
+ end
60
+ def write_data(data)
61
+ write("#{size(data)}\0")
62
+ write(data)
63
+ end
64
+ def write_entry(name, data)
65
+ write(name)
66
+ write_data(data)
67
+ end
68
+ end
69
+
metadata ADDED
@@ -0,0 +1,52 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ampk
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.5
5
+ platform: ruby
6
+ authors:
7
+ - Geoff Youngs
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-08-26 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: " AMPK is simple archive format which allows contents to be encrypted
14
+ or signed\n \tusing a public/private key system.\n \t\n Entities (i.e. files) can
15
+ be stored encrypted or signed with a private key.\n"
16
+ email: git@intersect-uk.co.uk
17
+ executables:
18
+ - ampk
19
+ extensions: []
20
+ extra_rdoc_files: []
21
+ files:
22
+ - bin/ampk
23
+ - lib/ampk/writer.rb
24
+ - lib/ampk/reader.rb
25
+ - lib/ampk/crypto.rb
26
+ - lib/ampk/version.rb
27
+ - lib/ampk.rb
28
+ homepage: http://github.com/livelink/ampk
29
+ licenses:
30
+ - MIT
31
+ metadata: {}
32
+ post_install_message:
33
+ rdoc_options: []
34
+ require_paths:
35
+ - lib
36
+ required_ruby_version: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ required_rubygems_version: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ requirements: []
47
+ rubyforge_project:
48
+ rubygems_version: 2.0.3
49
+ signing_key:
50
+ specification_version: 4
51
+ summary: Simple signed/encrypted archive format library
52
+ test_files: []