r509 0.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (162) hide show
  1. data/README.md +447 -0
  2. data/Rakefile +38 -0
  3. data/bin/r509 +96 -0
  4. data/bin/r509-parse +35 -0
  5. data/doc/R509.html +154 -0
  6. data/doc/R509/Cert.html +3954 -0
  7. data/doc/R509/Cert/Extensions.html +360 -0
  8. data/doc/R509/Cert/Extensions/AuthorityInfoAccess.html +391 -0
  9. data/doc/R509/Cert/Extensions/AuthorityKeyIdentifier.html +148 -0
  10. data/doc/R509/Cert/Extensions/BasicConstraints.html +482 -0
  11. data/doc/R509/Cert/Extensions/CrlDistributionPoints.html +316 -0
  12. data/doc/R509/Cert/Extensions/ExtendedKeyUsage.html +780 -0
  13. data/doc/R509/Cert/Extensions/KeyUsage.html +1230 -0
  14. data/doc/R509/Cert/Extensions/SubjectAlternativeName.html +467 -0
  15. data/doc/R509/Cert/Extensions/SubjectKeyIdentifier.html +216 -0
  16. data/doc/R509/CertificateAuthority.html +126 -0
  17. data/doc/R509/CertificateAuthority/Signer.html +855 -0
  18. data/doc/R509/Config.html +127 -0
  19. data/doc/R509/Config/CaConfig.html +2144 -0
  20. data/doc/R509/Config/CaConfigPool.html +599 -0
  21. data/doc/R509/Config/CaProfile.html +656 -0
  22. data/doc/R509/Config/SubjectItemPolicy.html +578 -0
  23. data/doc/R509/Crl.html +126 -0
  24. data/doc/R509/Crl/Administrator.html +2077 -0
  25. data/doc/R509/Crl/Parser.html +1224 -0
  26. data/doc/R509/Csr.html +2248 -0
  27. data/doc/R509/IOHelpers.html +564 -0
  28. data/doc/R509/MessageDigest.html +396 -0
  29. data/doc/R509/NameSanitizer.html +319 -0
  30. data/doc/R509/Ocsp.html +128 -0
  31. data/doc/R509/Ocsp/Request.html +126 -0
  32. data/doc/R509/Ocsp/Request/Nonce.html +160 -0
  33. data/doc/R509/Ocsp/Response.html +837 -0
  34. data/doc/R509/OidMapper.html +393 -0
  35. data/doc/R509/PrivateKey.html +1647 -0
  36. data/doc/R509/R509Error.html +134 -0
  37. data/doc/R509/Spki.html +1424 -0
  38. data/doc/R509/Subject.html +836 -0
  39. data/doc/R509/Validity.html +160 -0
  40. data/doc/R509/Validity/Checker.html +320 -0
  41. data/doc/R509/Validity/DefaultChecker.html +283 -0
  42. data/doc/R509/Validity/DefaultWriter.html +330 -0
  43. data/doc/R509/Validity/Status.html +561 -0
  44. data/doc/R509/Validity/Writer.html +394 -0
  45. data/doc/_index.html +501 -0
  46. data/doc/class_list.html +53 -0
  47. data/doc/css/common.css +1 -0
  48. data/doc/css/full_list.css +57 -0
  49. data/doc/css/style.css +328 -0
  50. data/doc/file.README.html +534 -0
  51. data/doc/file.r509.html +149 -0
  52. data/doc/file_list.html +58 -0
  53. data/doc/frames.html +28 -0
  54. data/doc/index.html +534 -0
  55. data/doc/js/app.js +208 -0
  56. data/doc/js/full_list.js +173 -0
  57. data/doc/js/jquery.js +4 -0
  58. data/doc/methods_list.html +1932 -0
  59. data/doc/top-level-namespace.html +112 -0
  60. data/lib/r509.rb +22 -0
  61. data/lib/r509/cert.rb +414 -0
  62. data/lib/r509/cert/extensions.rb +309 -0
  63. data/lib/r509/certificateauthority.rb +290 -0
  64. data/lib/r509/config.rb +407 -0
  65. data/lib/r509/crl.rb +379 -0
  66. data/lib/r509/csr.rb +324 -0
  67. data/lib/r509/exceptions.rb +5 -0
  68. data/lib/r509/io_helpers.rb +52 -0
  69. data/lib/r509/messagedigest.rb +49 -0
  70. data/lib/r509/ocsp.rb +85 -0
  71. data/lib/r509/oidmapper.rb +32 -0
  72. data/lib/r509/privatekey.rb +185 -0
  73. data/lib/r509/spki.rb +112 -0
  74. data/lib/r509/subject.rb +133 -0
  75. data/lib/r509/validity.rb +92 -0
  76. data/lib/r509/version.rb +4 -0
  77. data/r509.yaml +73 -0
  78. data/spec/cert/extensions_spec.rb +632 -0
  79. data/spec/cert_spec.rb +321 -0
  80. data/spec/certificate_authority_spec.rb +260 -0
  81. data/spec/config_spec.rb +349 -0
  82. data/spec/crl_spec.rb +215 -0
  83. data/spec/csr_spec.rb +302 -0
  84. data/spec/fixtures.rb +233 -0
  85. data/spec/fixtures/cert1.der +0 -0
  86. data/spec/fixtures/cert1.pem +24 -0
  87. data/spec/fixtures/cert1_public_key_modulus.txt +1 -0
  88. data/spec/fixtures/cert3.p12 +0 -0
  89. data/spec/fixtures/cert3.pem +28 -0
  90. data/spec/fixtures/cert3_key.pem +27 -0
  91. data/spec/fixtures/cert3_key_des3.pem +30 -0
  92. data/spec/fixtures/cert4.pem +14 -0
  93. data/spec/fixtures/cert5.pem +30 -0
  94. data/spec/fixtures/cert6.pem +26 -0
  95. data/spec/fixtures/cert_expired.pem +26 -0
  96. data/spec/fixtures/cert_not_yet_valid.pem +26 -0
  97. data/spec/fixtures/cert_san.pem +27 -0
  98. data/spec/fixtures/cert_san2.pem +22 -0
  99. data/spec/fixtures/config_pool_test_minimal.yaml +15 -0
  100. data/spec/fixtures/config_test.yaml +41 -0
  101. data/spec/fixtures/config_test_engine_key.yaml +7 -0
  102. data/spec/fixtures/config_test_engine_no_key_name.yaml +6 -0
  103. data/spec/fixtures/config_test_minimal.yaml +7 -0
  104. data/spec/fixtures/config_test_password.yaml +7 -0
  105. data/spec/fixtures/config_test_various.yaml +100 -0
  106. data/spec/fixtures/crl_list_file.txt +1 -0
  107. data/spec/fixtures/crl_with_reason.pem +17 -0
  108. data/spec/fixtures/csr1.der +0 -0
  109. data/spec/fixtures/csr1.pem +17 -0
  110. data/spec/fixtures/csr1_key.der +0 -0
  111. data/spec/fixtures/csr1_key.pem +27 -0
  112. data/spec/fixtures/csr1_key_encrypted_des3.pem +30 -0
  113. data/spec/fixtures/csr1_newlines.pem +32 -0
  114. data/spec/fixtures/csr1_no_begin_end.pem +15 -0
  115. data/spec/fixtures/csr1_public_key_modulus.txt +1 -0
  116. data/spec/fixtures/csr2.pem +15 -0
  117. data/spec/fixtures/csr2_key.pem +27 -0
  118. data/spec/fixtures/csr3.pem +16 -0
  119. data/spec/fixtures/csr4.pem +25 -0
  120. data/spec/fixtures/csr_dsa.pem +15 -0
  121. data/spec/fixtures/csr_invalid_signature.pem +13 -0
  122. data/spec/fixtures/dsa_key.pem +20 -0
  123. data/spec/fixtures/key4.pem +27 -0
  124. data/spec/fixtures/key4_encrypted_des3.pem +30 -0
  125. data/spec/fixtures/missing_key_identifier_ca.cer +21 -0
  126. data/spec/fixtures/missing_key_identifier_ca.key +27 -0
  127. data/spec/fixtures/ocsptest.r509.local.pem +27 -0
  128. data/spec/fixtures/ocsptest.r509.local_ocsp_request.der +0 -0
  129. data/spec/fixtures/ocsptest2.r509.local.pem +27 -0
  130. data/spec/fixtures/second_ca.cer +26 -0
  131. data/spec/fixtures/second_ca.key +27 -0
  132. data/spec/fixtures/spkac.der +0 -0
  133. data/spec/fixtures/spkac.txt +1 -0
  134. data/spec/fixtures/spkac_dsa.txt +1 -0
  135. data/spec/fixtures/stca.pem +22 -0
  136. data/spec/fixtures/stca_ocsp_request.der +0 -0
  137. data/spec/fixtures/stca_ocsp_response.der +0 -0
  138. data/spec/fixtures/test1.csr +17 -0
  139. data/spec/fixtures/test_ca.cer +22 -0
  140. data/spec/fixtures/test_ca.key +28 -0
  141. data/spec/fixtures/test_ca.p12 +0 -0
  142. data/spec/fixtures/test_ca_des3.key +30 -0
  143. data/spec/fixtures/test_ca_ocsp.cer +26 -0
  144. data/spec/fixtures/test_ca_ocsp.key +27 -0
  145. data/spec/fixtures/test_ca_ocsp.p12 +0 -0
  146. data/spec/fixtures/test_ca_ocsp_chain.txt +48 -0
  147. data/spec/fixtures/test_ca_ocsp_response.der +0 -0
  148. data/spec/fixtures/test_ca_subroot.cer +26 -0
  149. data/spec/fixtures/test_ca_subroot.key +27 -0
  150. data/spec/fixtures/test_ca_subroot_ocsp.cer +25 -0
  151. data/spec/fixtures/test_ca_subroot_ocsp.key +27 -0
  152. data/spec/fixtures/test_ca_subroot_ocsp_response.der +0 -0
  153. data/spec/fixtures/unknown_oid.csr +17 -0
  154. data/spec/message_digest_spec.rb +89 -0
  155. data/spec/ocsp_spec.rb +111 -0
  156. data/spec/oid_mapper_spec.rb +31 -0
  157. data/spec/privatekey_spec.rb +198 -0
  158. data/spec/spec_helper.rb +14 -0
  159. data/spec/spki_spec.rb +157 -0
  160. data/spec/subject_spec.rb +203 -0
  161. data/spec/validity_spec.rb +98 -0
  162. metadata +257 -0
data/lib/r509/csr.rb ADDED
@@ -0,0 +1,324 @@
1
+ require 'openssl'
2
+ require 'r509/exceptions'
3
+ require 'r509/io_helpers'
4
+ require 'r509/privatekey'
5
+
6
+ module R509
7
+ # The primary certificate signing request object
8
+ class Csr
9
+ include R509::IOHelpers
10
+
11
+ attr_reader :san_names, :key, :subject, :req, :attributes, :message_digest
12
+ # @option opts [String,OpenSSL::X509::Request] :csr a csr
13
+ # @option opts [Symbol] :type :rsa/:dsa
14
+ # @option opts [Integer] :bit_strength
15
+ # @option opts [Array] :san_names List of domains to encode as subjectAltNames
16
+ # @option opts [R509::Subject,Array,OpenSSL::X509::Name] :subject array of subject items
17
+ # @example [['CN','langui.sh'],['ST','Illinois'],['L','Chicago'],['C','US'],['emailAddress','ca@langui.sh']]
18
+ # you can also pass OIDs (see tests)
19
+ # @option opts [String,R509::Cert,OpenSSL::X509::Certificate] :cert takes a cert (used for generating a CSR with the certificate's values)
20
+ # @option opts [R509::PrivateKey,String] :key optional private key to supply. either an unencrypted PEM/DER string or an R509::PrivateKey object (use the latter if you need password/hardware support)
21
+ def initialize(opts={})
22
+ if not opts.kind_of?(Hash)
23
+ raise ArgumentError, 'Must provide a hash of options'
24
+ end
25
+ if (opts.has_key?(:cert) and opts.has_key?(:subject)) or
26
+ (opts.has_key?(:cert) and opts.has_key?(:csr)) or
27
+ (opts.has_key?(:subject) and opts.has_key?(:csr))
28
+ raise ArgumentError, "Can only provide one of cert, subject, or csr"
29
+ end
30
+ @bit_strength = opts[:bit_strength] || 2048
31
+
32
+ if opts.has_key?(:key)
33
+ if opts[:key].kind_of?(R509::PrivateKey)
34
+ @key = opts[:key]
35
+ else
36
+ @key = R509::PrivateKey.new(:key => opts[:key])
37
+ end
38
+ end
39
+
40
+ @type = opts[:type] || :rsa
41
+ if @type != :rsa and @type != :dsa and @key.nil?
42
+ raise ArgumentError, 'Must provide :rsa or :dsa as type when key is nil'
43
+ end
44
+
45
+ if opts.has_key?(:cert)
46
+ domains = opts[:san_names] || []
47
+ parsed_domains = prefix_domains(domains)
48
+ cert_data = parse_cert(opts[:cert])
49
+ merged_domains = cert_data[:subjectAltName].concat(parsed_domains)
50
+ create_request(cert_data[:subject],merged_domains) #sets @req
51
+ elsif opts.has_key?(:subject)
52
+ domains = opts[:san_names] || []
53
+ parsed_domains = prefix_domains(domains)
54
+ create_request(opts[:subject], parsed_domains) #sets @req
55
+ elsif opts.has_key?(:csr)
56
+ if opts.has_key?(:san_names)
57
+ raise ArgumentError, "You can't add domains to an existing CSR"
58
+ end
59
+ parse_csr(opts[:csr])
60
+ else
61
+ raise ArgumentError, "Must provide one of cert, subject, or csr"
62
+ end
63
+
64
+ if dsa?
65
+ #only DSS1 is acceptable for DSA signing in OpenSSL < 1.0
66
+ #post-1.0 you can sign with anything, but let's be conservative
67
+ #see: http://www.ruby-doc.org/stdlib-1.9.3/libdoc/openssl/rdoc/OpenSSL/PKey/DSA.html
68
+ @message_digest = R509::MessageDigest.new('dss1')
69
+ elsif opts.has_key?(:message_digest)
70
+ @message_digest = R509::MessageDigest.new(opts[:message_digest])
71
+ else
72
+ @message_digest = R509::MessageDigest.new('sha1')
73
+ end
74
+
75
+ if not opts.has_key?(:csr)
76
+ @req.sign(@key.key, @message_digest.digest)
77
+ end
78
+ if not @key.nil? and not @req.verify(@key.public_key) then
79
+ raise R509Error, 'Key does not match request.'
80
+ end
81
+
82
+ end
83
+
84
+ # Helper method to quickly load a CSR from the filesystem
85
+ #
86
+ # @param [String] filename Path to file you want to load
87
+ # @return [R509::Csr] Csr object
88
+ def self.load_from_file( filename )
89
+ return R509::Csr.new(:csr => IOHelpers.read_data(filename) )
90
+ end
91
+
92
+ # @return [OpenSSL::PKey::RSA] public key
93
+ def public_key
94
+ if(@req.kind_of?(OpenSSL::X509::Request)) then
95
+ @req.public_key
96
+ end
97
+ end
98
+
99
+ # Verifies the integrity of the signature on the request
100
+ # @return [Boolean]
101
+ def verify_signature
102
+ @req.verify(public_key)
103
+ end
104
+
105
+ # @return [Boolean] Boolean of whether the object contains a private key
106
+ def has_private_key?
107
+ if not @key.nil?
108
+ true
109
+ else
110
+ false
111
+ end
112
+ end
113
+
114
+ # Converts the CSR into the PEM format
115
+ #
116
+ # @return [String] the CSR converted into PEM format.
117
+ def to_pem
118
+ @req.to_pem
119
+ end
120
+
121
+ alias :to_s :to_pem
122
+
123
+ # Converts the CSR into the DER format
124
+ #
125
+ # @return [String] the CSR converted into DER format.
126
+ def to_der
127
+ @req.to_der
128
+ end
129
+
130
+ # Writes the CSR into the PEM format
131
+ #
132
+ # @param [String, #write] filename_or_io Either a string of the path for
133
+ # the file that you'd like to write, or an IO-like object.
134
+ def write_pem(filename_or_io)
135
+ write_data(filename_or_io, @req.to_pem)
136
+ end
137
+
138
+ # Writes the CSR into the DER format
139
+ #
140
+ # @param [String, #write] filename_or_io Either a string of the path for
141
+ # the file that you'd like to write, or an IO-like object.
142
+ def write_der(filename_or_io)
143
+ write_data(filename_or_io, @req.to_der)
144
+ end
145
+
146
+ # Returns whether the public key is RSA
147
+ #
148
+ # @return [Boolean] true if the public key is RSA, false otherwise
149
+ def rsa?
150
+ @req.public_key.kind_of?(OpenSSL::PKey::RSA)
151
+ end
152
+
153
+ # Returns whether the public key is DSA
154
+ #
155
+ # @return [Boolean] true if the public key is DSA, false otherwise
156
+ def dsa?
157
+ @req.public_key.kind_of?(OpenSSL::PKey::DSA)
158
+ end
159
+
160
+ # Returns the bit strength of the key used to create the CSR
161
+ # @return [Integer] the integer bit strength.
162
+ def bit_strength
163
+ if self.rsa?
164
+ return @req.public_key.n.num_bits
165
+ elsif self.dsa?
166
+ return @req.public_key.p.num_bits
167
+ end
168
+ end
169
+
170
+ # Returns subject component
171
+ #
172
+ # @return [String] value of the subject component requested
173
+ def subject_component short_name
174
+ @req.subject.to_a.each do |element|
175
+ if element[0].downcase == short_name.downcase then
176
+ return element[1]
177
+ end
178
+ end
179
+ nil
180
+ end
181
+
182
+ # Returns signature algorithm
183
+ #
184
+ # @return [String] value of the signature algorithm. E.g. sha1WithRSAEncryption, sha256WithRSAEncryption, md5WithRSAEncryption
185
+ def signature_algorithm
186
+ @req.signature_algorithm
187
+ end
188
+
189
+ # Returns key algorithm (RSA/DSA)
190
+ #
191
+ # @return [String] value of the key algorithm. RSA or DSA
192
+ def key_algorithm
193
+ if @req.public_key.kind_of? OpenSSL::PKey::RSA then
194
+ 'RSA'
195
+ elsif @req.public_key.kind_of? OpenSSL::PKey::DSA then
196
+ 'DSA'
197
+ end
198
+ end
199
+
200
+ # Returns a hash structure you can pass to the Ca.
201
+ # You will want to call this method if you intend to alter the values
202
+ # and then pass them to the Ca class.
203
+ #
204
+ # @return [Hash] :subject and :san_names you can pass to Ca
205
+ def to_hash
206
+ { :subject => @subject.dup , :san_names => @san_names.dup }
207
+ end
208
+
209
+ private
210
+
211
+ def parse_csr(csr)
212
+ begin
213
+ @req = OpenSSL::X509::Request.new csr
214
+ rescue OpenSSL::X509::RequestError
215
+ #let's try to load this thing by handling a few
216
+ #common error cases
217
+ if csr.kind_of?(String)
218
+ #normalize line endings (really just for the next replace)
219
+ csr.gsub!(/\r\n?/, "\n")
220
+ #remove extraneous newlines
221
+ csr.gsub!(/^\s*\n/,'')
222
+ #and leading/trailing whitespace
223
+ csr.gsub!(/^\s*|\s*$/,'')
224
+ if not csr.match(/-----BEGIN.+-----/) and csr.match(/MII/)
225
+ #if csr is probably PEM (MII is the beginning of every base64
226
+ #encoded DER) then add the wrapping lines if they aren't provided.
227
+ #tools like Microsoft's xenroll do this.
228
+ csr = "-----BEGIN CERTIFICATE REQUEST-----\n"+csr+"\n-----END CERTIFICATE REQUEST-----"
229
+ end
230
+ end
231
+ #and now we try again...
232
+ @req = OpenSSL::X509::Request.new csr
233
+ end
234
+ @subject = R509::Subject.new(@req.subject)
235
+ @attributes = parse_attributes_from_csr(@req)
236
+ @san_names = @attributes['subjectAltName'] || []
237
+ end
238
+
239
+ def create_request(subject,domains=[])
240
+ domains.uniq! #de-duplicate the array
241
+ @req = OpenSSL::X509::Request.new
242
+ @req.version = 0
243
+ @subject = R509::Subject.new(subject)
244
+ @req.subject = @subject.name
245
+ if @key.nil?
246
+ @key = R509::PrivateKey.new(:type => @type,
247
+ :bit_strength => @bit_strength)
248
+ end
249
+ @req.public_key = @key.public_key
250
+ add_san_extension(domains)
251
+ @attributes = parse_attributes_from_csr(@req)
252
+ @san_names = @attributes['subjectAltName'] || []
253
+ end
254
+
255
+ # parses an existing cert to get data to add to new CSR
256
+ def parse_cert(cert)
257
+ domains_to_add = []
258
+ san_extension = nil
259
+ parsed_cert = OpenSSL::X509::Certificate.new(cert)
260
+ parsed_cert.extensions.each { |extension|
261
+ if (extension.oid == 'subjectAltName') then
262
+ domains_to_add = parse_san_extension(extension)
263
+ end
264
+ }
265
+ {:subject => parsed_cert.subject, :subjectAltName => domains_to_add}
266
+ end
267
+
268
+ # @return [Hash] attributes of a CSR
269
+ def parse_attributes_from_csr(req)
270
+ attributes = Hash.new
271
+ domains_from_csr = []
272
+ set = nil
273
+ req.attributes.each { |attribute|
274
+ if attribute.oid == 'extReq' then
275
+ set = OpenSSL::ASN1.decode attribute.value
276
+ end
277
+ }
278
+ if !set.nil? then
279
+ set.value.each { |set_value|
280
+ @seq = set_value
281
+ extensions = @seq.value.collect{|asn1ext| OpenSSL::X509::Extension.new(asn1ext) }
282
+ extensions.each { |ext|
283
+ attributes[ext.oid] = {'value' => ext.value, 'critical'=> ext.critical? }
284
+ if ext.oid == 'subjectAltName' then
285
+ domains_from_csr = ext.value.gsub(/DNS:/,'').split(',')
286
+ domains_from_csr = domains_from_csr.collect {|x| x.strip }
287
+ attributes[ext.oid] = domains_from_csr
288
+ end
289
+ }
290
+ }
291
+ end
292
+ attributes
293
+ end
294
+
295
+ #takes OpenSSL::X509::Extension object
296
+ def parse_san_extension(extension)
297
+ san_string = extension.value
298
+ stripped = []
299
+ san_string.split(',').each{ |name|
300
+ stripped.push name.strip
301
+ }
302
+ stripped
303
+ end
304
+
305
+ def add_san_extension(domains_to_add)
306
+ if(domains_to_add.size > 0) then
307
+ ef = OpenSSL::X509::ExtensionFactory.new
308
+ ex = []
309
+ ex << ef.create_extension("subjectAltName", domains_to_add.join(', '))
310
+ request_extension_set = OpenSSL::ASN1::Set([OpenSSL::ASN1::Sequence(ex)])
311
+ @req.add_attribute(OpenSSL::X509::Attribute.new("extReq", request_extension_set))
312
+ @san_names = strip_prefix(domains_to_add)
313
+ end
314
+ end
315
+
316
+ def prefix_domains(domains)
317
+ domains.map { |domain| 'DNS: '+domain }
318
+ end
319
+
320
+ def strip_prefix(domains)
321
+ domains.map{ |name| name.gsub(/DNS:/,'').strip }
322
+ end
323
+ end
324
+ end
@@ -0,0 +1,5 @@
1
+ module R509
2
+ #An error r509 sometimes raises. You know, when it feels like it.
3
+ class R509Error < StandardError
4
+ end
5
+ end
@@ -0,0 +1,52 @@
1
+ module R509
2
+ # helper methods for I/O
3
+ module IOHelpers
4
+ # Writes data into an IO or file
5
+ # @param [String, #write] filename_or_io Either a string of the path for
6
+ # the file that you'd like to write, or an IO-like object.
7
+ # @param [String] data The data that we want to write
8
+ def self.write_data(filename_or_io, data)
9
+ if filename_or_io.respond_to?(:write)
10
+ filename_or_io.write(data)
11
+ else
12
+ begin
13
+ file = File.open(filename_or_io, 'wb:ascii-8bit')
14
+ return file.write(data)
15
+ ensure
16
+ file.close()
17
+ end
18
+ end
19
+ end
20
+
21
+ # Reads data from an IO or file
22
+ # @param [String, #read] filename_or_io Either a string of the path for
23
+ # the file that you'd like to read, or an IO-like object.
24
+ def self.read_data(filename_or_io)
25
+ if filename_or_io.respond_to?(:read)
26
+ filename_or_io.read()
27
+ else
28
+ begin
29
+ file = File.open(filename_or_io, 'rb:ascii-8bit')
30
+ return file.read()
31
+ ensure
32
+ file.close() unless file.nil?
33
+ end
34
+ end
35
+ end
36
+
37
+ # Writes data into an IO or file
38
+ # @param [String, #write] filename_or_io Either a string of the path for
39
+ # the file that you'd like to write, or an IO-like object.
40
+ # @param [String] data The data that we want to write
41
+ def write_data(filename_or_io, data)
42
+ IOHelpers.write_data(filename_or_io, data)
43
+ end
44
+
45
+ # Reads data from an IO or file
46
+ # @param [String, #read] filename_or_io Either a string of the path for
47
+ # the file that you'd like to read, or an IO-like object.
48
+ def read_data(filename_or_io)
49
+ IOHelpers.read_data(filename_or_io)
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,49 @@
1
+ require 'openssl'
2
+
3
+ module R509
4
+ #MessageDigest allows you to specify MDs in a more friendly fashion
5
+ class MessageDigest
6
+ attr_reader :name, :digest
7
+
8
+ # @param [String,OpenSSL::Digest] arg
9
+ def initialize(arg)
10
+ if arg.kind_of?(String)
11
+ @name = arg.downcase
12
+ @digest = translate_name_to_digest
13
+ else
14
+ @digest = arg
15
+ @name = translate_digest_to_name
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ # @return [OpenSSL::Digest]
22
+ def translate_name_to_digest
23
+ case @name
24
+ when 'sha1' then OpenSSL::Digest::SHA1.new
25
+ when 'sha256' then OpenSSL::Digest::SHA256.new
26
+ when 'sha512' then OpenSSL::Digest::SHA512.new
27
+ when 'md5' then OpenSSL::Digest::MD5.new
28
+ when 'dss1' then OpenSSL::Digest::DSS1.new
29
+ else
30
+ @name = "sha1"
31
+ OpenSSL::Digest::SHA1.new
32
+ end
33
+ end
34
+
35
+ # @return [String]
36
+ def translate_digest_to_name
37
+ case @digest
38
+ when OpenSSL::Digest::SHA1 then 'sha1'
39
+ when OpenSSL::Digest::SHA256 then 'sha256'
40
+ when OpenSSL::Digest::SHA512 then 'sha512'
41
+ when OpenSSL::Digest::MD5 then 'md5'
42
+ when OpenSSL::Digest::DSS1 then 'dss1'
43
+ else
44
+ raise ArgumentError, "Unknown digest"
45
+ end
46
+ end
47
+ end
48
+ end
49
+
data/lib/r509/ocsp.rb ADDED
@@ -0,0 +1,85 @@
1
+ require 'openssl'
2
+ require 'r509/exceptions'
3
+ require 'r509/config'
4
+
5
+ #Ocsp module
6
+ module R509::Ocsp
7
+
8
+ #builds OCSP responses
9
+ class Response
10
+ # @param ocsp_response [OpenSSL::OCSP::Response]
11
+ def initialize(ocsp_response)
12
+ if not ocsp_response.kind_of?(OpenSSL::OCSP::Response)
13
+ raise R509::R509Error, 'You must pass an OpenSSL::OCSP::Response object to the constructor. See R509::Ocsp::Response.parse if you are trying to parse'
14
+ end
15
+ @ocsp_response = ocsp_response
16
+ end
17
+ # @param [String,OpenSSL::OCSP::Response] ocsp_string parses an existing response
18
+ # @return [R509::Ocsp::Response]
19
+ def self.parse(ocsp_string)
20
+ if ocsp_string.nil?
21
+ raise R509::R509Error, 'You must pass a DER encoded OCSP response to this method'
22
+ end
23
+ R509::Ocsp::Response.new(OpenSSL::OCSP::Response.new(ocsp_string))
24
+ end
25
+
26
+ # @return [OpenSSL::OCSP] response status of this response
27
+ def status
28
+ @ocsp_response.status
29
+ end
30
+
31
+ # @return [String] der encoded string
32
+ def to_der
33
+ @ocsp_response.to_der
34
+ end
35
+
36
+ # @return [OpenSSL::OCSP::BasicResponse]
37
+ def basic
38
+ @ocsp_response.basic
39
+ end
40
+
41
+ # @param [Array<OpenSSL::X509::Certificate>,OpenSSL::X509::Certificate] certs A cert or array of certs to verify against
42
+ # @return [Boolean] true if the response is valid according to the given root
43
+ def verify(certs)
44
+ store = OpenSSL::X509::Store.new
45
+ if certs.kind_of?(Array)
46
+ stack = certs
47
+ certs.each do |cert|
48
+ store.add_cert(cert)
49
+ end
50
+ else
51
+ stack = [certs]
52
+ store.add_cert(certs)
53
+ end
54
+
55
+ #suppress verbosity since #verify will output a warning if it does not match
56
+ #as well as returning false. we just want the boolean
57
+ original_verbosity = $VERBOSE
58
+ $VERBOSE = nil
59
+ #still a bit unclear on why we add to store and pass in array to verify
60
+ result = @ocsp_response.basic.verify(stack, store)
61
+ $VERBOSE = original_verbosity
62
+ return result
63
+ end
64
+
65
+ # @param [OpenSSL::OCSP::Request] ocsp_request the OCSP request whose nonce to check
66
+ # @return [R509::Ocsp::Request::Nonce::CONSTANT] the status code of the nonce check
67
+ def check_nonce(ocsp_request)
68
+ ocsp_request.check_nonce(@ocsp_response.basic)
69
+ end
70
+ end
71
+
72
+ #holds OCSP request related items
73
+ module Request
74
+ # contains constants r509 uses for OCSP responses
75
+ module Nonce
76
+ #these values are defined at
77
+ #http://www.ruby-doc.org/stdlib-1.9.3/libdoc/openssl/rdoc/OpenSSL/OCSP/Request.html
78
+ PRESENT_AND_EQUAL = 1
79
+ BOTH_ABSENT = 2
80
+ RESPONSE_ONLY = 3
81
+ NOT_EQUAL = 0
82
+ REQUEST_ONLY = -1
83
+ end
84
+ end
85
+ end