r509 0.8

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.
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