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