truby_license 0.1.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.
@@ -0,0 +1,210 @@
1
+ require "base64"
2
+ require "openssl"
3
+ require "zlib"
4
+ require "javabean_xml"
5
+
6
+
7
+ class TrubyLicense
8
+ class TrubyException < Exception ; end
9
+ class PrivateKeyNeeded < TrubyException ; end
10
+ class InvalidLicense < TrubyException ; end
11
+ class InvalidPassword < TrubyException ; end
12
+
13
+ LicenseData = Struct.new :consumerType,
14
+ :notBefore,
15
+ :notAfter,
16
+ :extra,
17
+ :subject,
18
+ :holder,
19
+ :issued,
20
+ :issuer
21
+
22
+ X500Principal = Struct.new :name
23
+
24
+ # Creates an instance able to read and write licenses using the
25
+ # supplied password and key. For writing licenses the key needs to be a
26
+ # private key, otherwise a public key will do.
27
+ def initialize password, key
28
+ raise ArgumentError.new("password must be a String") unless password.is_a? String
29
+ raise ArgumentError.new("key must be an OpenSSL DSA key") unless key.is_a? OpenSSL::PKey::DSA
30
+ @password = password
31
+ @key = key
32
+
33
+ # to hold the ciphers used in the crypt method
34
+ @cipher ||= {}
35
+
36
+ # some extra parameters TrueLicense uses
37
+ @iterations = 2005
38
+ @salt = "\xCE\xFB\xDE\xAC\x05\x02\x19q"
39
+ end
40
+
41
+ def deserialize_license license_blob
42
+ begin
43
+ license_data = gunzip(decrypt(license_blob))
44
+ rescue OpenSSL::Cipher::CipherError
45
+ raise InvalidPassword.new "Could not decrypt license blob"
46
+ rescue Zlib::GzipFile::Error
47
+ raise InvalidLicense.new "Invalid format of license blob: not gzipped corrrectly"
48
+ end
49
+
50
+
51
+ JavabeanXml.from_xml license_data,
52
+
53
+ :string => lambda { |value, properties| value },
54
+ "de.schlichtherle.xml.GenericCertificate" => lambda { |v, properties|
55
+ sig = b64d(properties[:signature])
56
+ license =properties[:encoded]
57
+ algorithm = properties[:signatureAlgorithm]
58
+ encoding = properties[:signatureEncoding]
59
+ unless algorithm == "SHA1withDSA"
60
+ raise NotImplementedError.new(
61
+ "signature algorithm %s has not been implemented".
62
+ % algorithm
63
+ )
64
+ end
65
+ unless encoding == "US-ASCII/Base64"
66
+ raise NotImplementedError.new(
67
+ "signature encoding %s has not been implemented".
68
+ % encoding
69
+ )
70
+ end
71
+ unless verify_signature(license, sig)
72
+ raise InvalidLicense.new("License signature mismatch")
73
+ end
74
+ JavabeanXml.from_xml(license,
75
+ :long => lambda { |value, p| value.to_i },
76
+ :string => lambda { |value, p| value },
77
+ "java.util.Date" => lambda { |value, p| Time.at(value / 1000) },
78
+ "javax.security.auth.x500.X500Principal" => lambda { |value, p| X500Principal.new value },
79
+ "de.schlichtherle.license.LicenseContent" => lambda {|value, properties|
80
+ ld = LicenseData.new
81
+ ld.members.each do |prop|
82
+ ld[prop] = properties[prop.to_sym]
83
+ end
84
+ ld
85
+ }
86
+ )
87
+ }
88
+
89
+ end
90
+
91
+ def serialize_license license_data
92
+ lic_data = license_data.clone # allows us to make changes
93
+ unless @key.private?
94
+ raise PrivateKeyNeeded.new("Cannot use a public key to encrypt the license")
95
+ end
96
+ # make sure issuer and holder are properly wrapped in X500Principal objects
97
+ [:issuer, :holder].each do |prop|
98
+ if lic_data[prop].is_a? String
99
+ lic_data[prop] = X500Principal.new lic_data[prop]
100
+ end
101
+ end
102
+ lic_data.consumerType = lic_data.consumerType.to_s
103
+ inner = JavabeanXml.to_xml lic_data,
104
+ LicenseData => lambda { |value|
105
+ {
106
+ :class => "de.schlichtherle.license.LicenseContent",
107
+ :properties => lic_data.members.inject({}) {|props, prop|
108
+ props[prop.to_sym] = lic_data[prop]
109
+ props
110
+ }
111
+ }
112
+ },
113
+ X500Principal => lambda { |value|
114
+ {
115
+ :class => "javax.security.auth.x500.X500Principal",
116
+ :value => {
117
+ :class => :string,
118
+ :value => value.name
119
+ }
120
+ }
121
+ },
122
+ Time => lambda { |value|
123
+ {
124
+ :class => "java.util.Date",
125
+ :value => {
126
+ :class => :long,
127
+ :value => value.to_i * 1000
128
+ }
129
+ }
130
+ },
131
+ String => lambda { |value|
132
+ {
133
+ :class => :string,
134
+ :value => value
135
+ }
136
+ }
137
+ outer = JavabeanXml.to_xml(
138
+ {
139
+ :class => "de.schlichtherle.xml.GenericCertificate",
140
+ :properties => {
141
+ :encoded => inner,
142
+ :signature => b64e(sign(inner)),
143
+ :signatureAlgorithm => "SHA1withDSA",
144
+ :signatureEncoding => "US-ASCII/Base64"
145
+ }
146
+ },
147
+ String => lambda { |value|
148
+ {
149
+ :class => :string,
150
+ :value => value
151
+ }
152
+ }
153
+ )
154
+ encrypt(gzip(outer))
155
+ end
156
+
157
+
158
+ private
159
+
160
+ def gzip data
161
+ gzipped = ""
162
+ zf = Zlib::GzipWriter.new(StringIO.new(gzipped))
163
+ zf << data
164
+ zf.close
165
+ gzipped
166
+ end
167
+
168
+ def gunzip data
169
+ Zlib::GzipReader.new(StringIO.new(data)).read
170
+ end
171
+
172
+ def sha1 data
173
+ OpenSSL::Digest::SHA1.digest(data)
174
+ end
175
+
176
+ def b64e data
177
+ Base64.encode64 data
178
+ end
179
+
180
+ def b64d string
181
+ Base64.decode64 string
182
+ end
183
+
184
+ def sign data
185
+ @key.syssign sha1(data)
186
+ end
187
+
188
+ def verify_signature data, signature
189
+ @key.sysverify(sha1(data), signature)
190
+ end
191
+
192
+ def crypt mode, data
193
+ unless @cipher[mode]
194
+ @cipher[mode] = OpenSSL::Cipher.new "DES"
195
+ @cipher[mode].send mode
196
+ @cipher[mode].pkcs5_keyivgen @password, @salt, @iterations
197
+ end
198
+ c = @cipher[mode]
199
+ c.update(data) + c.final
200
+ end
201
+
202
+ def decrypt data
203
+ crypt :decrypt, data
204
+ end
205
+
206
+ def encrypt data
207
+ crypt :encrypt, data
208
+ end
209
+
210
+ end
@@ -0,0 +1,150 @@
1
+ require "rubygems"
2
+ require "test/unit"
3
+ require "truby_license"
4
+ class Fixnum
5
+ def seconds
6
+ self
7
+ end
8
+ def minutes
9
+ 60 * seconds
10
+ end
11
+ def hours
12
+ 60 * minutes
13
+ end
14
+ def days
15
+ 24 * hours
16
+ end
17
+ def weeks
18
+ 7 * days
19
+ end
20
+ def months
21
+ 30 * days
22
+ end
23
+ def years
24
+ 365 * days
25
+ end
26
+ def from_now
27
+ Time.at(Time.now.to_i + self)
28
+ end
29
+ def ago
30
+ Time.at(Time.now.to_i - self)
31
+ end
32
+ end
33
+ class TestTrubyLicense < Test::Unit::TestCase
34
+
35
+ def initialize *args
36
+ super *args
37
+ @key1 = { :priv => OpenSSL::PKey::DSA.generate(1024) }
38
+ @key1[:pub] = @key1[:priv].public_key
39
+
40
+ @key2 = { :priv => OpenSSL::PKey::DSA.generate(1024) }
41
+ @key2[:pub] = @key2[:priv].public_key
42
+ end
43
+
44
+ def test_serialize_deserialize
45
+ ld = TrubyLicense::LicenseData.new
46
+
47
+ ld.consumerType = "0"
48
+ ld.notBefore = 5.days.ago
49
+ ld.notAfter = 10.days.from_now
50
+ ld.extra = "an <html><document /></html>"
51
+ ld.subject = "Some subject"
52
+ ld.holder = TrubyLicense::X500Principal.new "CN=Einar Boson"
53
+ ld.issued = Time.at(Time.now.to_i) # strip ns
54
+ ld.issuer = TrubyLicense::X500Principal.new "CN=Einar Boson"
55
+ tl_priv = TrubyLicense.new "my secret password", @key1[:priv]
56
+ tl_pub = TrubyLicense.new "my secret password", @key1[:pub]
57
+
58
+ encoded = tl_priv.serialize_license ld
59
+ decoded = tl_pub.deserialize_license encoded
60
+ ld.each_pair do |prop, val|
61
+ assert_equal val, decoded[prop], "License data should not change through serialization/deserialization"
62
+ end
63
+ end
64
+
65
+ def test_automatic_x500_wrapping
66
+ ld = TrubyLicense::LicenseData.new
67
+
68
+ ld.consumerType = "0"
69
+ ld.notBefore = 5.days.ago
70
+ ld.notAfter = 10.days.from_now
71
+ ld.extra = "an <html><document /></html>"
72
+ ld.subject = "Some subject"
73
+ ld.holder = "CN=Einar Boson1"
74
+ ld.issued = Time.at(Time.now.to_i) # strip ns
75
+ ld.issuer = "CN=Einar Boson2"
76
+
77
+ tl_priv = TrubyLicense.new "my secret password", @key1[:priv]
78
+ tl_pub = TrubyLicense.new "my secret password", @key1[:pub]
79
+
80
+ encoded = tl_priv.serialize_license ld
81
+ decoded = tl_pub.deserialize_license encoded
82
+
83
+ assert decoded.holder.respond_to?(:name) && decoded.holder.name == "CN=Einar Boson1",
84
+ "holder should be automatically wrapped as an X500 object"
85
+ assert decoded.issuer.respond_to?(:name) && decoded.issuer.name == "CN=Einar Boson2",
86
+ "issuer should be automatically wrapped as an X500 object"
87
+
88
+ end
89
+
90
+ def test_exception_on_serializing_With_public_key
91
+ ld = TrubyLicense::LicenseData.new
92
+
93
+ ld.consumerType = "0"
94
+ ld.notBefore = 5.days.ago
95
+ ld.notAfter = 10.days.from_now
96
+ ld.extra = "an <html><document /></html>"
97
+ ld.subject = "Some subject"
98
+ ld.holder = "CN=Einar Boson1"
99
+ ld.issued = Time.at(Time.now.to_i) # strip ns
100
+ ld.issuer = "CN=Einar Boson2"
101
+
102
+ tl = TrubyLicense.new "my secret password", @key1[:pub]
103
+
104
+ assert_raise TrubyLicense::PrivateKeyNeeded do
105
+ tl.serialize_license ld
106
+ end
107
+ end
108
+
109
+ def test_license_invalid_if_signed_with_other_key
110
+ ld = TrubyLicense::LicenseData.new
111
+
112
+ ld.consumerType = "0"
113
+ ld.notBefore = 5.days.ago
114
+ ld.notAfter = 10.days.from_now
115
+ ld.extra = "an <html><document /></html>"
116
+ ld.subject = "Some subject"
117
+ ld.holder = TrubyLicense::X500Principal.new "CN=Einar Boson"
118
+ ld.issued = Time.at(Time.now.to_i) # strip ns
119
+ ld.issuer = TrubyLicense::X500Principal.new "CN=Einar Boson"
120
+ tl_priv = TrubyLicense.new "my secret password", @key1[:priv]
121
+ t2_pub = TrubyLicense.new "my secret password", @key2[:pub]
122
+
123
+ encoded = tl_priv.serialize_license ld
124
+
125
+ assert_raise TrubyLicense::InvalidLicense do
126
+ decoded = t2_pub.deserialize_license encoded
127
+ end
128
+ end
129
+
130
+ def test_license_invalid_if_wrong_password
131
+ ld = TrubyLicense::LicenseData.new
132
+
133
+ ld.consumerType = "0"
134
+ ld.notBefore = 5.days.ago
135
+ ld.notAfter = 10.days.from_now
136
+ ld.extra = "an <html><document /></html>"
137
+ ld.subject = "Some subject"
138
+ ld.holder = TrubyLicense::X500Principal.new "CN=Einar Boson"
139
+ ld.issued = Time.at(Time.now.to_i) # strip ns
140
+ ld.issuer = TrubyLicense::X500Principal.new "CN=Einar Boson"
141
+ tl_priv = TrubyLicense.new "my secret password", @key1[:priv]
142
+ t1_pub = TrubyLicense.new "wrong password", @key1[:pub]
143
+
144
+ encoded = tl_priv.serialize_license ld
145
+
146
+ assert_raise TrubyLicense::InvalidPassword do
147
+ decoded = t1_pub.deserialize_license encoded
148
+ end
149
+ end
150
+ end
metadata ADDED
@@ -0,0 +1,97 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: truby_license
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - "Einar Magn\xC3\xBAs Boson"
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-12-22 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: nokogiri
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ~>
27
+ - !ruby/object:Gem::Version
28
+ hash: 5
29
+ segments:
30
+ - 1
31
+ - 5
32
+ version: "1.5"
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: builder
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ hash: 7
44
+ segments:
45
+ - 3
46
+ - 0
47
+ version: "3.0"
48
+ type: :runtime
49
+ version_requirements: *id002
50
+ description: Create and read True License files in ruby
51
+ email: einar.boson@gmail.com
52
+ executables: []
53
+
54
+ extensions: []
55
+
56
+ extra_rdoc_files: []
57
+
58
+ files:
59
+ - lib/truby_license.rb
60
+ - test/test_truby_license.rb
61
+ homepage: http://github.com/einarmagnus/truby_license
62
+ licenses:
63
+ - MIT
64
+ post_install_message:
65
+ rdoc_options: []
66
+
67
+ require_paths:
68
+ - lib
69
+ required_ruby_version: !ruby/object:Gem::Requirement
70
+ none: false
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ hash: 57
75
+ segments:
76
+ - 1
77
+ - 8
78
+ - 7
79
+ version: 1.8.7
80
+ required_rubygems_version: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ hash: 3
86
+ segments:
87
+ - 0
88
+ version: "0"
89
+ requirements: []
90
+
91
+ rubyforge_project:
92
+ rubygems_version: 1.8.13
93
+ signing_key:
94
+ specification_version: 3
95
+ summary: True License for Ruby
96
+ test_files:
97
+ - test/test_truby_license.rb