mack-encryption 0.8.1 → 0.8.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,535 @@
1
+ require 'ezcrypto'
2
+ require 'net/http'
3
+ =begin rdoc
4
+
5
+ These modules provides a simple ruby like way to create and verify digital signatures.
6
+
7
+ == License
8
+
9
+ ActiveCrypto and EzCrypto are released under the MIT license.
10
+
11
+
12
+ == Support
13
+
14
+ To contact the author, send mail to pelleb@gmail.com
15
+
16
+ Also see my blogs at:
17
+ http://stakeventures.com and
18
+ http://neubia.com
19
+
20
+ This project was based on code used in my project StakeItOut, where you can securely share web services with your partners.
21
+ https://stakeitout.com
22
+
23
+ (C) 2005 Pelle Braendgaard
24
+
25
+ =end
26
+ module EzCrypto
27
+
28
+ =begin rdoc
29
+ The signer is used for signing stuff. It encapsulates the functionality of a private key.
30
+ =end
31
+ class Signer
32
+ =begin rdoc
33
+ Initialize a Signer with a OpenSSL Private Key. You generally should not call new directly.
34
+ Unless you are interfacing with your own underlying OpenSSL code.
35
+ =end
36
+ def initialize(priv,options = {})
37
+ @priv=priv
38
+ end
39
+
40
+ =begin rdoc
41
+ Generate a new keypair. Defaults to 2048 bit RSA.
42
+ =end
43
+ def self.generate(strength=2048,type=:rsa)
44
+ key_class=case type
45
+ when :dsa
46
+ OpenSSL::PKey::DSA
47
+ else
48
+ OpenSSL::PKey::RSA
49
+ end
50
+ EzCrypto::Signer.new(key_class.generate(strength))
51
+ end
52
+
53
+ =begin rdoc
54
+ Decode a PEM encoded Private Key and return a signer. Takes an optional password
55
+ =end
56
+ def self.decode(encoded,password=nil)
57
+ begin
58
+ EzCrypto::Signer.new(OpenSSL::PKey::RSA.new( encoded,password))
59
+ rescue
60
+ EzCrypto::Signer.new(OpenSSL::PKey::DSA.new( encoded,password))
61
+ end
62
+ end
63
+
64
+ =begin rdoc
65
+ Decode a PEM encoded Private Key file and return a signer. Takes an optional password
66
+ =end
67
+ def self.from_file(filename,password=nil)
68
+ file = File.read( filename )
69
+ decode(file,password)
70
+ end
71
+
72
+ =begin rdoc
73
+ Returns the OpenSSL Public Key object. You normally do not need to use this.
74
+ =end
75
+ def public_key
76
+ @priv.public_key
77
+ end
78
+
79
+ =begin rdoc
80
+ Returns the corresponding Verifier object.
81
+ =end
82
+ def verifier
83
+ Verifier.new(public_key)
84
+ end
85
+
86
+ =begin rdoc
87
+ Returns the OpenSSL Private Key object. You normally do not need to use this.
88
+ =end
89
+ def private_key
90
+ @priv
91
+ end
92
+
93
+ =begin rdoc
94
+ signs data using the private key and the corresponding digest function. SHA1 for RSA and DSS1 for DSA.
95
+ 99% of signing use these parameters.
96
+ Email a request or send me a patch if you have other requirements.
97
+ =end
98
+ def sign(data)
99
+ if rsa?
100
+ @priv.sign(OpenSSL::Digest::SHA1.new,data)
101
+ elsif dsa?
102
+ @priv.sign(OpenSSL::Digest::DSS1.new,data)
103
+ end
104
+ end
105
+
106
+ =begin rdoc
107
+ Returns true if it is a RSA private key
108
+ =end
109
+ def rsa?
110
+ @priv.is_a? OpenSSL::PKey::RSA
111
+ end
112
+
113
+ =begin rdoc
114
+ Returns true if it is a DSA private key
115
+ =end
116
+ def dsa?
117
+ @priv.is_a? OpenSSL::PKey::DSA
118
+ end
119
+
120
+ end
121
+
122
+ =begin rdoc
123
+ The Verifier is used for verifying signatures. If you use the decode or
124
+ from_file methods you can use either raw PEM encoded public keys or certificate.
125
+ =end
126
+ class Verifier
127
+ =begin rdoc
128
+ Initializes a Verifier using a OpenSSL public key object.
129
+ =end
130
+ def initialize(pub)
131
+ @pub=pub
132
+ end
133
+
134
+ =begin rdoc
135
+ Decodes a PEM encoded Certificate or Public Key and returns a Verifier object.
136
+ =end
137
+ def self.decode(encoded)
138
+ case encoded
139
+ when /-----BEGIN CERTIFICATE-----/
140
+ EzCrypto::Certificate.new(OpenSSL::X509::Certificate.new( encoded))
141
+ else
142
+ begin
143
+ EzCrypto::Verifier.new(OpenSSL::PKey::RSA.new( encoded))
144
+ rescue
145
+ EzCrypto::Verifier.new(OpenSSL::PKey::DSA.new( encoded))
146
+ end
147
+ end
148
+ end
149
+
150
+ =begin rdoc
151
+ Decodes a PEM encoded Certificate or Public Key from a file and returns a Verifier object.
152
+ =end
153
+ def self.from_file(filename)
154
+ file = File.read( filename )
155
+ decode(file)
156
+ end
157
+
158
+ =begin rdoc
159
+ Load a certificate or public key from PKYP based on it's hex digest
160
+ =end
161
+ def self.from_pkyp(digest)
162
+ digest=digest.strip.downcase
163
+ if digest=~/[0123456789abcdef]{40}/
164
+ # Net::HTTP.start("localhost", 9000) do |query|
165
+ Net::HTTP.start("pkyp.org", 80) do |query|
166
+ response=query.get "/#{digest}.pem"
167
+ if response.code=="200"
168
+ decode(response.body)
169
+ else
170
+ raise "Error occured (#{response.code}): #{response.body}"
171
+ end
172
+ end
173
+ else
174
+ raise "Invalid digest"
175
+ end
176
+ end
177
+
178
+ =begin rdoc
179
+ Decodes all certificates or public keys in a file and returns an array.
180
+ =end
181
+ def self.load_all_from_file(filename)
182
+ file = File.read( filename )
183
+ certs=[]
184
+ count=0
185
+ file.split( %q{-----BEGIN}).each do |pem|
186
+ if pem and pem!=""
187
+ pem="-----BEGIN#{pem}\n"
188
+ cert=decode(pem)
189
+ if cert.is_a? EzCrypto::Verifier
190
+ certs<<cert
191
+ end
192
+ end
193
+ end
194
+ certs
195
+ end
196
+
197
+ =begin rdoc
198
+ Is the Verifier a Certificate or not.
199
+ =end
200
+ def cert?
201
+ false
202
+ end
203
+
204
+ =begin rdoc
205
+ Returns the OpenSSL public key object. You would normally not need to use this.
206
+ =end
207
+ def public_key
208
+ @pub
209
+ end
210
+
211
+ =begin rdoc
212
+ Returns the SHA1 hexdigest of the DER encoded public key. This can be used as a unique key identifier.
213
+ =end
214
+ def digest
215
+ Digest::SHA1.hexdigest(@pub.to_der)
216
+ end
217
+ =begin rdoc
218
+ Is this a RSA key?
219
+ =end
220
+ def rsa?
221
+ @pub.is_a? OpenSSL::PKey::RSA
222
+ end
223
+ =begin rdoc
224
+ Is this a DSA key?
225
+ =end
226
+ def dsa?
227
+ @pub.is_a? OpenSSL::PKey::DSA
228
+ end
229
+
230
+
231
+ =begin rdoc
232
+ Returns true if the public key signed the given data.
233
+ =end
234
+ def verify(sig,data)
235
+ if rsa?
236
+ @pub.verify( OpenSSL::Digest::SHA1.new, sig, data )
237
+ elsif dsa?
238
+ @pub.verify( OpenSSL::Digest::DSS1.new, sig, data )
239
+ else
240
+ false
241
+ end
242
+ end
243
+
244
+ =begin rdoc
245
+ Register the public key or certificate at PKYP
246
+ =end
247
+ def register_with_pkyp
248
+ send_to_pkyp(@pub.to_s)
249
+ end
250
+
251
+ protected
252
+
253
+ def send_to_pkyp(pem)
254
+ # Net::HTTP.start("localhost", 9000) do |query|
255
+ Net::HTTP.start("pkyp.org", 80) do |query|
256
+ output=URI.escape(pem).gsub("+","%2b")
257
+ response=query.post "/register","body="+output
258
+ if response.code=="302"
259
+ response["Location"]=~/([0123456789abcdef]{40}$)/
260
+ $1
261
+ else
262
+ raise "Error occured (#{response.code}): #{response.body}"
263
+ end
264
+ end
265
+ end
266
+
267
+ end
268
+
269
+ =begin rdoc
270
+ Certificate provides functionality to make it easy to extract information from a Certificate.
271
+ This also provides all the same functionality as a Verifier.
272
+ =end
273
+ class Certificate < Verifier
274
+
275
+ =begin rdoc
276
+ Intialize with a OpenSSL cert object.
277
+ =end
278
+ def initialize(cert)
279
+ super(cert.public_key)
280
+ @cert=cert
281
+ end
282
+
283
+ =begin rdoc
284
+ Returns true
285
+ =end
286
+ def cert?
287
+ true
288
+ end
289
+
290
+ =begin rdoc
291
+ Register the certificate at PKYP
292
+ =end
293
+ def register_with_pkyp
294
+ send_to_pkyp(@cert.to_s)
295
+ end
296
+
297
+ =begin rdoc
298
+ Returns the SHA1 hex digest of a the DER encoded certificate. This is useful as a unique identifier.
299
+ =end
300
+ def cert_digest
301
+ Digest::SHA1.hexdigest(@cert.to_der)
302
+ end
303
+
304
+ =begin rdoc
305
+ Returns a Name object containt the subject of the certificate. The subject in X509 speak is the details of the certificate owner.
306
+ =end
307
+ def subject
308
+ @subject=EzCrypto::Name.new(@cert.subject) unless @subject
309
+ @subject
310
+ end
311
+
312
+ =begin rdoc
313
+ Returns a Name object containt the issuer of the certificate.
314
+ =end
315
+ def issuer
316
+ @issuer=EzCrypto::Name.new(@cert.issuer) unless @issuer
317
+ @issuer
318
+ end
319
+
320
+ =begin rdoc
321
+ Returns the issuers serial number for this certificate
322
+ =end
323
+ def serial
324
+ @cert.serial
325
+ end
326
+
327
+ =begin rdoc
328
+ Returns the OpenSSL Certificate object
329
+ =end
330
+ def cert
331
+ @cert
332
+ end
333
+
334
+ =begin rdoc
335
+ Returns the certificates valid not before date.
336
+ =end
337
+ def not_before
338
+ @cert.not_before
339
+ end
340
+
341
+ =begin rdoc
342
+ Returns the certificates valid not after date.
343
+ =end
344
+ def not_after
345
+ @cert.not_after
346
+ end
347
+
348
+ =begin rdoc
349
+ Is this certificate valid at this point in time. Note this only checks if it is valid with respect to time.
350
+ It is important to realize that it does not check with any CRL or OCSP services to see if the certificate was
351
+ revoked.
352
+ =end
353
+ def valid?(time=Time.now.utc)
354
+ time.to_i>self.not_before.to_i && time.to_i<self.not_after.to_i
355
+ end
356
+
357
+ =begin rdoc
358
+ Returns the hash of extensions available in the certificate. These are not always present.
359
+ =end
360
+ def extensions
361
+ unless @extensions
362
+ @extensions={}
363
+ cert.extensions.each {|e| @extensions[e.oid]=e.value} if cert.extensions
364
+ end
365
+ @extensions
366
+ end
367
+
368
+ =begin rdoc
369
+ Any methods defined in Name can be used here. This means you can do cert.email rather than cert.subject.email.
370
+ =end
371
+ def method_missing(method)
372
+ subject.send method
373
+ end
374
+
375
+ end
376
+
377
+ =begin rdoc
378
+ A handy ruby wrapper around OpenSSL's Name object. This was created to make it really easy to extract information out of the certificate.
379
+ =end
380
+ class Name
381
+ =begin rdoc
382
+ Initializes the Name object with the underlying OpenSSL Name object. You generally do not need to use this.
383
+ Rather use the Certificates subject or issuer methods.
384
+ =end
385
+ def initialize(name)
386
+ @name=name
387
+ @attributes={}
388
+ name.to_s.split(/\//).each do |field|
389
+ key, val = field.split(/=/,2)
390
+ if key
391
+ @attributes[key.to_sym]=val
392
+ end
393
+ end
394
+ end
395
+
396
+ =begin rdoc
397
+ Returns the full name object in classic horrible X500 format.
398
+ =end
399
+ def to_s
400
+ @name.to_s
401
+ end
402
+
403
+ =begin rdoc
404
+ Returns the email if present in the name
405
+ =end
406
+ def email
407
+ self[:emailAddress]
408
+ end
409
+ =begin rdoc
410
+ The 2 letter country code of the name
411
+ =end
412
+ def country
413
+ self[:C]
414
+ end
415
+ alias_method :c,:country
416
+ =begin rdoc
417
+ The state or province code
418
+ =end
419
+ def state
420
+ self[:ST]
421
+ end
422
+ alias_method :st,:state
423
+ alias_method :province,:state
424
+
425
+ =begin rdoc
426
+ The locality
427
+ =end
428
+ def locality
429
+ self[:L]
430
+ end
431
+ alias_method :l,:locality
432
+
433
+ =begin rdoc
434
+ The Organizational Unit
435
+ =end
436
+ def organizational_unit
437
+ self[:OU]
438
+ end
439
+ alias_method :ou,:organizational_unit
440
+ alias_method :organisational_unit,:organizational_unit
441
+
442
+ =begin rdoc
443
+ The Organization
444
+ =end
445
+ def organization
446
+ self[:O]
447
+ end
448
+ alias_method :o,:organization
449
+ alias_method :organisation,:organization
450
+
451
+ =begin rdoc
452
+ The common name. For SSL this means the domain name. For personal certificates it is the name.
453
+ =end
454
+ def common_name
455
+ self[:CN]
456
+ end
457
+ alias_method :name,:common_name
458
+ alias_method :cn,:common_name
459
+
460
+ =begin rdoc
461
+ Lookup fields in the certificate.
462
+ =end
463
+ def [](attr_key)
464
+ @attributes[attr_key.to_sym]
465
+ end
466
+
467
+ def method_missing(method)
468
+ self[method]
469
+ end
470
+
471
+ end
472
+
473
+ =begin rdoc
474
+ Wraps around the OpenSSL trust store. This allows you to decide which certificates you trust.
475
+
476
+ You can either point it at a path which contains a OpenSSL trust store (see OpenSSL for more) or build it up manually.
477
+
478
+ For a certificate to verify you need the issuer and the issuers issuers certs added to the Trust store.
479
+
480
+ NOTE: Currently this does not support CRL's or OCSP. We may add support for this later.
481
+ =end
482
+ class TrustStore
483
+
484
+ =begin rdoc
485
+ Create a trust store of normally trusted root certificates as found in a browser. Extracted from Safari.
486
+ =end
487
+ def self.default_trusted
488
+ load_from_file(File.dirname(__FILE__) + "/trusted.pem")
489
+ end
490
+ =begin rdoc
491
+ Create a trust store from a list of certificates in a pem file.
492
+ These certificates should just be listed one after each other.
493
+ =end
494
+ def self.load_from_file(file)
495
+ store=TrustStore.new
496
+ EzCrypto::Verifier.load_all_from_file(file).each do |cert|
497
+ store.add cert
498
+ end
499
+ store
500
+ end
501
+ =begin rdoc
502
+ Create trust store with an optional list of paths of openssl trust stores.
503
+ =end
504
+ def initialize(*paths)
505
+ @store=OpenSSL::X509::Store.new
506
+ # @store.set_default_path paths.shift if paths.length>0
507
+ paths.each {|path| @store.add_path path}
508
+ end
509
+
510
+ =begin rdoc
511
+ Add either a EzCrypto::Certificate or a OpenSSL::X509::Cert object to the TrustStore. This should be a trusted certificate such as a CA's issuer certificate.
512
+ =end
513
+ def add(obj)
514
+ if obj.kind_of?(EzCrypto::Certificate)
515
+ @store.add_cert obj.cert
516
+ elsif obj.kind_of?(OpenSSL::X509::Certificate)
517
+ @store.add_cert obj
518
+ else
519
+ raise "unsupported object type"
520
+ end
521
+ end
522
+ =begin rdoc
523
+ Returns true if either the EzCrypto::Certificate or OpenSSL::X509::Cert object is verified using issuer certificates in the trust store.
524
+ =end
525
+ def verify(cert)
526
+ if cert.kind_of?(EzCrypto::Certificate)
527
+ @store.verify cert.cert
528
+ elsif cert.kind_of?(OpenSSL::X509::Certificate)
529
+ @store.verify cert
530
+ else
531
+ false
532
+ end
533
+ end
534
+ end
535
+ end