bequest 0.0.3

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.
@@ -0,0 +1,3 @@
1
+ sandbox
2
+ Gemfile.lock
3
+ *.gem
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm --create use 1.8.7@bequest
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in document_builder.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Andy White
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,68 @@
1
+ # Bequest
2
+
3
+ There might be times when you (the bequestor) want to provide data to a third party (the bequestee) for a limited time and/or only on a specific machine. Bequest enables password, MAC address and expiry-based protection of data via a single, binary, encrypted license file.
4
+
5
+
6
+ ## Installation
7
+
8
+ Add this line to your application's Gemfile:
9
+
10
+ gem 'bequest'
11
+
12
+ And then execute:
13
+
14
+ $ bundle
15
+
16
+ Or install it yourself as:
17
+
18
+ $ gem install bequest
19
+
20
+ ## Usage
21
+
22
+ The bequestor would run:
23
+
24
+ Bequest::License.create('path/to/source/data', 'path/to/out/file',
25
+ :expires_at => Time.now + 1.year,
26
+ :password => 'secret word or phrase',
27
+ :mac_addr => 'bequestee:MAC:address:for:example')
28
+
29
+ Parameters:
30
+
31
+ 1. Source data file - this is the secret data you want made securely available to the client, it can be plain text or binary
32
+ 2. Out file - the license file that will be created
33
+
34
+ Options (at least ONE of :password or :mac_addr must be set):
35
+
36
+ * :expires_at - time as a Ruby Time object, no expiry if omitted or set to nil
37
+ * :password - this can be any word or phrase and will be prompted for (if set and not supplied as an option) when the license is loaded
38
+ * :mac_addr - if set this will be read from the local machine when the bequestee loads the license if not supplied as an option
39
+
40
+ The serialised, binary license file contains a main checksum and a body. The body is composed of:
41
+
42
+ * The encrypted expiry time
43
+ * Password and MAC address booleans
44
+ * The compressed, encrypted source data
45
+ * The initialisation vector
46
+
47
+ The main checksum is of the body as a joined array, before it was serialised in the license file. It is used to validate the integrity of the license file.
48
+
49
+ The client would run:
50
+
51
+ lic, data = Bequest::License.load('path/to/lic/file',
52
+ :password => 'whatever', :mac_addr => 'whatever')
53
+
54
+ The password will be prompted for if set and *:password* not supplied. The first MAC address of the local machine will be used if set and *:mac_addr* not supplied. *data* will be set to nil if authentication fails or it has expired. *lic* will always be populated regardless of success and may be queried as follows:
55
+
56
+ lic.valid? # => true or false
57
+ lic.expired? # => true or false
58
+ lic.expires_at # => Ruby Time object
59
+ lic.status # => :ok, :expired, :unauthorized, or :tampered
60
+
61
+
62
+ ## Contributing
63
+
64
+ 1. Fork it
65
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
66
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
67
+ 4. Push to the branch (`git push origin my-new-feature`)
68
+ 5. Create new Pull Request
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,20 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'bequest/version'
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "bequest"
8
+ s.version = Bequest::VERSION
9
+ s.authors = ["Andy White"]
10
+ s.email = ["andy@wireworldmedia.co.uk"]
11
+ s.description = %q{License secure data}
12
+ s.summary = %q{Bequest enables password, MAC address and expiry-based validation and secure data provision via a single license file.}
13
+ s.homepage = ""
14
+ s.files = `git ls-files`.split($/)
15
+ s.executables = s.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
16
+ s.test_files = s.files.grep(%r{^(test|spec|features)/})
17
+ s.require_paths = ["lib"]
18
+ s.add_runtime_dependency "macaddr", ">= 1.6.1"
19
+ s.add_development_dependency "rspec", ">= 0"
20
+ end
@@ -0,0 +1,3 @@
1
+ require 'bequest/data'
2
+ require 'bequest/license'
3
+ require 'bequest/version'
@@ -0,0 +1,132 @@
1
+ require 'openssl'
2
+ require 'digest/sha2'
3
+ require 'digest/md5'
4
+ require 'zlib'
5
+ require 'macaddr'
6
+
7
+ module Bequest
8
+ class Data
9
+ def initialize(secret_data, opts = {})
10
+ opts = {:expires_at => nil, :password => '', :mac_addr => ''}.merge(opts)
11
+ compressed = Zlib::Deflate.deflate(secret_data)
12
+ key = key(opts[:password], opts[:mac_addr])
13
+ iv = Digest::MD5.hexdigest(rand.to_s)
14
+ encrypted_data = encrypt(compressed, key, iv)
15
+ encrypted_expires_at = opts[:expires_at] ? encrypt(opts[:expires_at].to_i.to_s, key, iv) : nil
16
+
17
+ body = [encrypted_expires_at, opts[:password].any?, opts[:mac_addr].any?, encrypted_data, iv]
18
+ checksum = Digest::MD5.hexdigest(body.join)
19
+ @data = [checksum, body]
20
+ end
21
+
22
+ def dump(path)
23
+ FileUtils.mkdir_p(File.dirname(path))
24
+ File.open(path, "w") { |f| f.write(Marshal::dump(self)) }
25
+ end
26
+
27
+ def unpack(password, mac_addr)
28
+ password = prompt_if_required(password)
29
+ mac_addr = get_if_required(mac_addr)
30
+
31
+ if Digest::MD5.hexdigest(body.join) == checksum
32
+ compressed = decrypt(encrypted_data, key(password, mac_addr), iv)
33
+
34
+ begin
35
+ data = Zlib::Inflate.inflate(compressed)
36
+
37
+ if encrypted_expires_at
38
+ expires_at = Time.at(decrypt(encrypted_expires_at, key(password, mac_addr), iv).to_i)
39
+
40
+ if expires_at.to_i < Time.now.to_i
41
+ [nil, :expired, expires_at]
42
+ else
43
+ [data, :ok, expires_at]
44
+ end
45
+ else
46
+ [data, :ok, nil]
47
+ end
48
+ rescue
49
+ [nil, :unauthorized, nil]
50
+ end
51
+ else
52
+ [nil, :tampered, nil]
53
+ end
54
+ end
55
+
56
+ private
57
+
58
+ def checksum
59
+ @data[0]
60
+ end
61
+
62
+ def body
63
+ @data[1]
64
+ end
65
+
66
+ def encrypted_expires_at
67
+ body[0]
68
+ end
69
+
70
+ def need_password?
71
+ body[1]
72
+ end
73
+
74
+ def need_mac_addr?
75
+ body[2]
76
+ end
77
+
78
+ def encrypted_data
79
+ body[3]
80
+ end
81
+
82
+ def iv
83
+ body[4]
84
+ end
85
+
86
+ def prompt_if_required(password)
87
+ if need_password? && password.nil?
88
+ STDOUT.puts "Password: "
89
+ STDOUT.flush
90
+ gets.chomp
91
+ else
92
+ password
93
+ end
94
+ end
95
+
96
+ def get_if_required(mac_addr)
97
+ if need_mac_addr? && mac_addr.nil?
98
+ Mac.addr
99
+ else
100
+ mac_addr
101
+ end
102
+ end
103
+
104
+ def key(password, mac_addr)
105
+ sha256 = Digest::SHA2.new(256)
106
+ sha256.digest(
107
+ 'ZggthtmuGfDWy4D' + (password || '') + 'gt5gIrfMcgyb8ii' + (mac_addr || '') + 'C1pq3gi65SX2ckx'
108
+ )
109
+ end
110
+
111
+ def encrypt(data, key, iv)
112
+ aes = OpenSSL::Cipher.new("AES-256-CFB")
113
+ aes.encrypt
114
+ aes.key = key
115
+ aes.iv = iv
116
+ aes.update(data) + aes.final
117
+ end
118
+
119
+ def decrypt(data, key, iv)
120
+ aes = OpenSSL::Cipher.new("AES-256-CFB")
121
+ aes.decrypt
122
+ aes.key = key
123
+ aes.iv = iv
124
+
125
+ begin
126
+ aes.update(data) + aes.final
127
+ rescue
128
+ false
129
+ end
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,38 @@
1
+ module Bequest
2
+ class License
3
+ attr_reader :expires_at, :status
4
+
5
+ def initialize(status, expires_at)
6
+ @status = status
7
+ @expires_at = expires_at
8
+ end
9
+
10
+ class << self
11
+ def create(data_path, out_path, opts = {})
12
+ if (opts[:password]||'').any? || (opts[:mac_addr]||'').any?
13
+ Data.new(File.read(data_path), opts).dump(out_path)
14
+ else
15
+ puts "At least ONE of password or mac_addr required"
16
+ end
17
+ end
18
+
19
+ def load(lic_file_path, opts = {})
20
+ begin
21
+ data = Marshal::load(File.read(lic_file_path))
22
+ original_data, status, expires_at = data.unpack(opts[:password], opts[:mac_addr])
23
+ [self.new(status, expires_at), original_data]
24
+ rescue
25
+ [self.new(:tampered, nil), nil]
26
+ end
27
+ end
28
+ end
29
+
30
+ def valid?
31
+ @status == :ok
32
+ end
33
+
34
+ def expired?
35
+ expires_at ? expires_at < Time.now : nil
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,3 @@
1
+ module Bequest
2
+ VERSION = "0.0.3"
3
+ end
Binary file
Binary file
@@ -0,0 +1,44 @@
1
+ This is
2
+ top secret.
3
+
4
+ ...and here's a secret camel:
5
+
6
+ __
7
+ .--. .' `.
8
+ .' . :\ / : L
9
+ F :\ / . : | .-._
10
+ / : \/ J .' ___\
11
+ J : / : : L /--' ``.
12
+ F : J | .<'.o. `-'>
13
+ / J L \_>. .--w)
14
+ J / \_/| . `-__|
15
+ F / ` -' /|)
16
+ | : J ' |
17
+ .' ': | . : \
18
+ / J : |L
19
+ F | \ ||
20
+ F . | : |
21
+ F | ; . : : F
22
+ / | : J
23
+ J J ) ; F
24
+ | L / .:' J
25
+ .-'F: L ./ :: : . F
26
+ `-'F: .\ `:.J :::. J
27
+ J ::\ `:| |::::\ |
28
+ J |:`. J :`:::\ F
29
+ L :':/ \ `-`. \ : `:::| .-'
30
+ | / L >--\ :::|`. .-'
31
+ J J | | L . :::: :`, /
32
+ L F J ) | >:: : /
33
+ | J L F \ .-.:' . /
34
+ ): | J / `- | | .--'
35
+ / | |: J L J J )
36
+ L | |: | L F| /
37
+ \: J \: L \ / L |
38
+ L | \ | F| | )
39
+ J F \ J J | |J
40
+ L| \ \ | | | L
41
+ J L \ \ F \ F |
42
+ L\ \ \ J | J L
43
+ /__\_________)_`._)_ |_/ \_____
44
+ "" `"""
Binary file
@@ -0,0 +1,8 @@
1
+ def sandbox
2
+ File.expand_path(File.join('..', '..', 'sandbox'), __FILE__)
3
+ end
4
+
5
+ def set_sandbox
6
+ system "rm -fr #{sandbox} && mkdir #{sandbox}"
7
+ end
8
+
@@ -0,0 +1,279 @@
1
+ require 'bequest'
2
+ require 'helper'
3
+
4
+ module Bequest
5
+ describe License do
6
+ before(:each) do
7
+ set_sandbox
8
+ @lic_file = './sandbox/lic.dat'
9
+ @tampered_lic_file = './samples/tampered_lic.dat'
10
+ @tampered_checksum_lic_file = './samples/tampered_checksum_lic.dat'
11
+ @data_file = './samples/secret.txt'
12
+ @soon = Time.now + 10
13
+ @just_passed = Time.now - 10
14
+ @password = 'secret'
15
+ @mac_addr = 'mac:addr'
16
+ end
17
+
18
+ describe "Bequester makes a new license" do
19
+ before(:each) do
20
+ License.create(@data_file, @lic_file, :expires_at => @soon, :password => @password)
21
+ @lic, data = License.load(@lic_file, :password => @password)
22
+ end
23
+
24
+ it "creates a file" do
25
+ File.exist?(@lic_file).should be_true
26
+ end
27
+
28
+ it "has a correct expiry time" do
29
+ @lic.expires_at.to_i.should == @soon.to_i
30
+ end
31
+
32
+ it "requires at least ONE of password or mac_addr" do
33
+ @lic = License.create(@data_file, @lic_file)
34
+ @lic.should be_nil
35
+ end
36
+
37
+ it "requires at least ONE of password or mac_addr to have a length" do
38
+ @lic = License.create(@data_file, @lic_file, :password => '')
39
+ @lic.should be_nil
40
+ end
41
+ end
42
+
43
+ describe "Bequestee loads an 'in date', password protected license file" do
44
+ before(:each) do
45
+ License.create(@data_file, @lic_file, :expires_at => @soon, :password => @password)
46
+ end
47
+
48
+ describe "Correct password supplied" do
49
+ before(:each) do
50
+ @lic, @data = License.load(@lic_file, :password => @password)
51
+ end
52
+
53
+ it "should be a license" do
54
+ @lic.class.should == License
55
+ end
56
+
57
+ it "should be valid" do
58
+ @lic.valid?.should be_true
59
+ end
60
+
61
+ it "status should be OK" do
62
+ @lic.status.should == :ok
63
+ end
64
+
65
+ it "has a correct expiry time" do
66
+ @lic.expires_at.to_i.should == @soon.to_i
67
+ end
68
+
69
+ it "should not be expired" do
70
+ @lic.expired?.should == false
71
+ end
72
+
73
+ it "should yield original data" do
74
+ @data.should == File.read(@data_file)
75
+ end
76
+ end
77
+
78
+ describe "Wrong password supplied" do
79
+ before(:each) do
80
+ @lic, @data = License.load(@lic_file, :password => 'plainly wrong')
81
+ end
82
+
83
+ it "should not be valid" do
84
+ @lic.valid?.should be_false
85
+ end
86
+
87
+ it "status should be unauthorized" do
88
+ @lic.status.should == :unauthorized
89
+ end
90
+
91
+ it "should have a nil expiry time" do
92
+ @lic.expires_at.should be_nil
93
+ end
94
+
95
+ it "expired? should be nil" do
96
+ @lic.expired?.should be_nil
97
+ end
98
+
99
+ it "should yield no data" do
100
+ @data.should be_nil
101
+ end
102
+ end
103
+
104
+ describe "No password supplied" do
105
+ it "should prompt" do
106
+ STDOUT.should_receive(:puts).with("Password: ")
107
+ @lic, @data = License.load(@lic_file)
108
+ end
109
+ end
110
+ end
111
+
112
+ describe "Bequestee loads an expired, password protected license file" do
113
+ before(:each) do
114
+ lic = License.create(@data_file, @lic_file, :expires_at => @just_passed, :password => @password)
115
+ @lic, @data = License.load(@lic_file, :password => @password)
116
+ end
117
+
118
+ it "should not be valid" do
119
+ @lic.valid?.should be_false
120
+ end
121
+
122
+ it "status should be expired" do
123
+ @lic.status.should == :expired
124
+ end
125
+
126
+ it "should have correct expiry time" do
127
+ @lic.expires_at.to_i.should == @just_passed.to_i
128
+ end
129
+
130
+ it "expired? should be true" do
131
+ @lic.expired?.should be_true
132
+ end
133
+
134
+ it "should yield no data" do
135
+ @data.should be_nil
136
+ end
137
+ end
138
+
139
+ describe "Bequestee loads a password protected license file with a tampered body" do
140
+ before(:each) do
141
+ @lic, @data = License.load(@tampered_lic_file, :password => @password)
142
+ end
143
+
144
+ it "should not be valid" do
145
+ @lic.valid?.should be_false
146
+ end
147
+
148
+ it "status should be tampered" do
149
+ @lic.status.should == :tampered
150
+ end
151
+
152
+ it "should have nil expiry time" do
153
+ @lic.expires_at.should be_nil
154
+ end
155
+
156
+ it "expired? should be nil" do
157
+ @lic.expired?.should be_nil
158
+ end
159
+
160
+ it "should yield no data" do
161
+ @data.should be_nil
162
+ end
163
+ end
164
+
165
+ describe "Bequestee loads a password protected license file with a tampered checksum" do
166
+ before(:each) do
167
+ @lic, @data = License.load(@tampered_checksum_lic_file, :password => @password)
168
+ end
169
+
170
+ it "should not be valid" do
171
+ @lic.valid?.should be_false
172
+ end
173
+
174
+ it "status should be tampered" do
175
+ @lic.status.should == :tampered
176
+ end
177
+
178
+ it "should have nil expiry time" do
179
+ @lic.expires_at.should be_nil
180
+ end
181
+
182
+ it "expired? should be nil" do
183
+ @lic.expired?.should be_nil
184
+ end
185
+
186
+ it "should yield no data" do
187
+ @data.should be_nil
188
+ end
189
+ end
190
+
191
+ describe "Bequestee loads an imortal, password protected license file" do
192
+ before(:each) do
193
+ lic = License.create(@data_file, @lic_file, :password => @password)
194
+ @lic, @data = License.load(@lic_file, :password => @password)
195
+ end
196
+
197
+ it "should be valid" do
198
+ @lic.valid?.should be_true
199
+ end
200
+
201
+ it "status should be ok" do
202
+ @lic.status.should == :ok
203
+ end
204
+
205
+ it "should have nil expiry time" do
206
+ @lic.expires_at.should be_nil
207
+ end
208
+
209
+ it "expired? should be nil" do
210
+ @lic.expired?.should be_nil
211
+ end
212
+
213
+ it "should yield original data" do
214
+ @data.should == File.read(@data_file)
215
+ end
216
+ end
217
+
218
+ describe "Bequestee loads an imortal, MAC protected (for bequestee machine) license file" do
219
+ before(:each) do
220
+ lic = License.create(@data_file, @lic_file, :mac_addr => Mac.addr)
221
+ end
222
+
223
+ describe "no MAC supplied" do
224
+ before(:each) do
225
+ @lic, @data = License.load(@lic_file)
226
+ end
227
+
228
+ it "should be valid" do
229
+ @lic.valid?.should be_true
230
+ end
231
+
232
+ it "status should be ok" do
233
+ @lic.status.should == :ok
234
+ end
235
+
236
+ it "should yield original data" do
237
+ @data.should == File.read(@data_file)
238
+ end
239
+ end
240
+
241
+ describe "Wrong MAC supplied" do
242
+ before(:each) do
243
+ @lic, @data = License.load(@lic_file, :mac_addr => 'very:wrong:mac:addr:indeed')
244
+ end
245
+
246
+ it "should not be valid" do
247
+ @lic.valid?.should be_false
248
+ end
249
+
250
+ it "status should be unauthorized" do
251
+ @lic.status.should == :unauthorized
252
+ end
253
+
254
+ it "should have a nil expiry time" do
255
+ @lic.expires_at.should be_nil
256
+ end
257
+
258
+ it "expired? should be nil" do
259
+ @lic.expired?.should be_nil
260
+ end
261
+
262
+ it "should yield no data" do
263
+ @data.should be_nil
264
+ end
265
+ end
266
+ end
267
+
268
+ describe "Bequestee loads a binary file" do
269
+ before(:each) do
270
+ License.create('./samples/hello.exe', @lic_file, :password => @password)
271
+ @lic, @data = License.load(@lic_file, :password => @password)
272
+ end
273
+
274
+ it "binary data matches original" do
275
+ @data == open('./samples/hello.exe', "rb") {|io| io.read }
276
+ end
277
+ end
278
+ end
279
+ end
metadata ADDED
@@ -0,0 +1,113 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bequest
3
+ version: !ruby/object:Gem::Version
4
+ hash: 25
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 3
10
+ version: 0.0.3
11
+ platform: ruby
12
+ authors:
13
+ - Andy White
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2013-02-21 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: macaddr
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ hash: 13
29
+ segments:
30
+ - 1
31
+ - 6
32
+ - 1
33
+ version: 1.6.1
34
+ type: :runtime
35
+ version_requirements: *id001
36
+ - !ruby/object:Gem::Dependency
37
+ name: rspec
38
+ prerelease: false
39
+ requirement: &id002 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ hash: 3
45
+ segments:
46
+ - 0
47
+ version: "0"
48
+ type: :development
49
+ version_requirements: *id002
50
+ description: License secure data
51
+ email:
52
+ - andy@wireworldmedia.co.uk
53
+ executables: []
54
+
55
+ extensions: []
56
+
57
+ extra_rdoc_files: []
58
+
59
+ files:
60
+ - .gitignore
61
+ - .rvmrc
62
+ - Gemfile
63
+ - LICENSE.txt
64
+ - README.md
65
+ - Rakefile
66
+ - bequest.gemspec
67
+ - lib/bequest.rb
68
+ - lib/bequest/data.rb
69
+ - lib/bequest/license.rb
70
+ - lib/bequest/version.rb
71
+ - samples/hello.exe
72
+ - samples/lic.dat
73
+ - samples/secret.txt
74
+ - samples/tampered_checksum_lic.dat
75
+ - samples/tampered_lic.dat
76
+ - spec/helper.rb
77
+ - spec/license_spec.rb
78
+ homepage: ""
79
+ licenses: []
80
+
81
+ post_install_message:
82
+ rdoc_options: []
83
+
84
+ require_paths:
85
+ - lib
86
+ required_ruby_version: !ruby/object:Gem::Requirement
87
+ none: false
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ hash: 3
92
+ segments:
93
+ - 0
94
+ version: "0"
95
+ required_rubygems_version: !ruby/object:Gem::Requirement
96
+ none: false
97
+ requirements:
98
+ - - ">="
99
+ - !ruby/object:Gem::Version
100
+ hash: 3
101
+ segments:
102
+ - 0
103
+ version: "0"
104
+ requirements: []
105
+
106
+ rubyforge_project:
107
+ rubygems_version: 1.8.25
108
+ signing_key:
109
+ specification_version: 3
110
+ summary: Bequest enables password, MAC address and expiry-based validation and secure data provision via a single license file.
111
+ test_files:
112
+ - spec/helper.rb
113
+ - spec/license_spec.rb