r509 0.8
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +447 -0
- data/Rakefile +38 -0
- data/bin/r509 +96 -0
- data/bin/r509-parse +35 -0
- data/doc/R509.html +154 -0
- data/doc/R509/Cert.html +3954 -0
- data/doc/R509/Cert/Extensions.html +360 -0
- data/doc/R509/Cert/Extensions/AuthorityInfoAccess.html +391 -0
- data/doc/R509/Cert/Extensions/AuthorityKeyIdentifier.html +148 -0
- data/doc/R509/Cert/Extensions/BasicConstraints.html +482 -0
- data/doc/R509/Cert/Extensions/CrlDistributionPoints.html +316 -0
- data/doc/R509/Cert/Extensions/ExtendedKeyUsage.html +780 -0
- data/doc/R509/Cert/Extensions/KeyUsage.html +1230 -0
- data/doc/R509/Cert/Extensions/SubjectAlternativeName.html +467 -0
- data/doc/R509/Cert/Extensions/SubjectKeyIdentifier.html +216 -0
- data/doc/R509/CertificateAuthority.html +126 -0
- data/doc/R509/CertificateAuthority/Signer.html +855 -0
- data/doc/R509/Config.html +127 -0
- data/doc/R509/Config/CaConfig.html +2144 -0
- data/doc/R509/Config/CaConfigPool.html +599 -0
- data/doc/R509/Config/CaProfile.html +656 -0
- data/doc/R509/Config/SubjectItemPolicy.html +578 -0
- data/doc/R509/Crl.html +126 -0
- data/doc/R509/Crl/Administrator.html +2077 -0
- data/doc/R509/Crl/Parser.html +1224 -0
- data/doc/R509/Csr.html +2248 -0
- data/doc/R509/IOHelpers.html +564 -0
- data/doc/R509/MessageDigest.html +396 -0
- data/doc/R509/NameSanitizer.html +319 -0
- data/doc/R509/Ocsp.html +128 -0
- data/doc/R509/Ocsp/Request.html +126 -0
- data/doc/R509/Ocsp/Request/Nonce.html +160 -0
- data/doc/R509/Ocsp/Response.html +837 -0
- data/doc/R509/OidMapper.html +393 -0
- data/doc/R509/PrivateKey.html +1647 -0
- data/doc/R509/R509Error.html +134 -0
- data/doc/R509/Spki.html +1424 -0
- data/doc/R509/Subject.html +836 -0
- data/doc/R509/Validity.html +160 -0
- data/doc/R509/Validity/Checker.html +320 -0
- data/doc/R509/Validity/DefaultChecker.html +283 -0
- data/doc/R509/Validity/DefaultWriter.html +330 -0
- data/doc/R509/Validity/Status.html +561 -0
- data/doc/R509/Validity/Writer.html +394 -0
- data/doc/_index.html +501 -0
- data/doc/class_list.html +53 -0
- data/doc/css/common.css +1 -0
- data/doc/css/full_list.css +57 -0
- data/doc/css/style.css +328 -0
- data/doc/file.README.html +534 -0
- data/doc/file.r509.html +149 -0
- data/doc/file_list.html +58 -0
- data/doc/frames.html +28 -0
- data/doc/index.html +534 -0
- data/doc/js/app.js +208 -0
- data/doc/js/full_list.js +173 -0
- data/doc/js/jquery.js +4 -0
- data/doc/methods_list.html +1932 -0
- data/doc/top-level-namespace.html +112 -0
- data/lib/r509.rb +22 -0
- data/lib/r509/cert.rb +414 -0
- data/lib/r509/cert/extensions.rb +309 -0
- data/lib/r509/certificateauthority.rb +290 -0
- data/lib/r509/config.rb +407 -0
- data/lib/r509/crl.rb +379 -0
- data/lib/r509/csr.rb +324 -0
- data/lib/r509/exceptions.rb +5 -0
- data/lib/r509/io_helpers.rb +52 -0
- data/lib/r509/messagedigest.rb +49 -0
- data/lib/r509/ocsp.rb +85 -0
- data/lib/r509/oidmapper.rb +32 -0
- data/lib/r509/privatekey.rb +185 -0
- data/lib/r509/spki.rb +112 -0
- data/lib/r509/subject.rb +133 -0
- data/lib/r509/validity.rb +92 -0
- data/lib/r509/version.rb +4 -0
- data/r509.yaml +73 -0
- data/spec/cert/extensions_spec.rb +632 -0
- data/spec/cert_spec.rb +321 -0
- data/spec/certificate_authority_spec.rb +260 -0
- data/spec/config_spec.rb +349 -0
- data/spec/crl_spec.rb +215 -0
- data/spec/csr_spec.rb +302 -0
- data/spec/fixtures.rb +233 -0
- data/spec/fixtures/cert1.der +0 -0
- data/spec/fixtures/cert1.pem +24 -0
- data/spec/fixtures/cert1_public_key_modulus.txt +1 -0
- data/spec/fixtures/cert3.p12 +0 -0
- data/spec/fixtures/cert3.pem +28 -0
- data/spec/fixtures/cert3_key.pem +27 -0
- data/spec/fixtures/cert3_key_des3.pem +30 -0
- data/spec/fixtures/cert4.pem +14 -0
- data/spec/fixtures/cert5.pem +30 -0
- data/spec/fixtures/cert6.pem +26 -0
- data/spec/fixtures/cert_expired.pem +26 -0
- data/spec/fixtures/cert_not_yet_valid.pem +26 -0
- data/spec/fixtures/cert_san.pem +27 -0
- data/spec/fixtures/cert_san2.pem +22 -0
- data/spec/fixtures/config_pool_test_minimal.yaml +15 -0
- data/spec/fixtures/config_test.yaml +41 -0
- data/spec/fixtures/config_test_engine_key.yaml +7 -0
- data/spec/fixtures/config_test_engine_no_key_name.yaml +6 -0
- data/spec/fixtures/config_test_minimal.yaml +7 -0
- data/spec/fixtures/config_test_password.yaml +7 -0
- data/spec/fixtures/config_test_various.yaml +100 -0
- data/spec/fixtures/crl_list_file.txt +1 -0
- data/spec/fixtures/crl_with_reason.pem +17 -0
- data/spec/fixtures/csr1.der +0 -0
- data/spec/fixtures/csr1.pem +17 -0
- data/spec/fixtures/csr1_key.der +0 -0
- data/spec/fixtures/csr1_key.pem +27 -0
- data/spec/fixtures/csr1_key_encrypted_des3.pem +30 -0
- data/spec/fixtures/csr1_newlines.pem +32 -0
- data/spec/fixtures/csr1_no_begin_end.pem +15 -0
- data/spec/fixtures/csr1_public_key_modulus.txt +1 -0
- data/spec/fixtures/csr2.pem +15 -0
- data/spec/fixtures/csr2_key.pem +27 -0
- data/spec/fixtures/csr3.pem +16 -0
- data/spec/fixtures/csr4.pem +25 -0
- data/spec/fixtures/csr_dsa.pem +15 -0
- data/spec/fixtures/csr_invalid_signature.pem +13 -0
- data/spec/fixtures/dsa_key.pem +20 -0
- data/spec/fixtures/key4.pem +27 -0
- data/spec/fixtures/key4_encrypted_des3.pem +30 -0
- data/spec/fixtures/missing_key_identifier_ca.cer +21 -0
- data/spec/fixtures/missing_key_identifier_ca.key +27 -0
- data/spec/fixtures/ocsptest.r509.local.pem +27 -0
- data/spec/fixtures/ocsptest.r509.local_ocsp_request.der +0 -0
- data/spec/fixtures/ocsptest2.r509.local.pem +27 -0
- data/spec/fixtures/second_ca.cer +26 -0
- data/spec/fixtures/second_ca.key +27 -0
- data/spec/fixtures/spkac.der +0 -0
- data/spec/fixtures/spkac.txt +1 -0
- data/spec/fixtures/spkac_dsa.txt +1 -0
- data/spec/fixtures/stca.pem +22 -0
- data/spec/fixtures/stca_ocsp_request.der +0 -0
- data/spec/fixtures/stca_ocsp_response.der +0 -0
- data/spec/fixtures/test1.csr +17 -0
- data/spec/fixtures/test_ca.cer +22 -0
- data/spec/fixtures/test_ca.key +28 -0
- data/spec/fixtures/test_ca.p12 +0 -0
- data/spec/fixtures/test_ca_des3.key +30 -0
- data/spec/fixtures/test_ca_ocsp.cer +26 -0
- data/spec/fixtures/test_ca_ocsp.key +27 -0
- data/spec/fixtures/test_ca_ocsp.p12 +0 -0
- data/spec/fixtures/test_ca_ocsp_chain.txt +48 -0
- data/spec/fixtures/test_ca_ocsp_response.der +0 -0
- data/spec/fixtures/test_ca_subroot.cer +26 -0
- data/spec/fixtures/test_ca_subroot.key +27 -0
- data/spec/fixtures/test_ca_subroot_ocsp.cer +25 -0
- data/spec/fixtures/test_ca_subroot_ocsp.key +27 -0
- data/spec/fixtures/test_ca_subroot_ocsp_response.der +0 -0
- data/spec/fixtures/unknown_oid.csr +17 -0
- data/spec/message_digest_spec.rb +89 -0
- data/spec/ocsp_spec.rb +111 -0
- data/spec/oid_mapper_spec.rb +31 -0
- data/spec/privatekey_spec.rb +198 -0
- data/spec/spec_helper.rb +14 -0
- data/spec/spki_spec.rb +157 -0
- data/spec/subject_spec.rb +203 -0
- data/spec/validity_spec.rb +98 -0
- 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,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
|