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/config.rb
ADDED
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
require 'yaml'
|
|
2
|
+
require 'openssl'
|
|
3
|
+
require 'r509/exceptions'
|
|
4
|
+
require 'r509/io_helpers'
|
|
5
|
+
require 'r509/subject'
|
|
6
|
+
require 'r509/privatekey'
|
|
7
|
+
require 'fileutils'
|
|
8
|
+
require 'pathname'
|
|
9
|
+
|
|
10
|
+
module R509
|
|
11
|
+
# Module to contain all configuration related classes (e.g. CaConfig, CaProfile, SubjectItemPolicy)
|
|
12
|
+
module Config
|
|
13
|
+
# Provides access to configuration profiles
|
|
14
|
+
class CaProfile
|
|
15
|
+
attr_reader :basic_constraints, :key_usage, :extended_key_usage,
|
|
16
|
+
:certificate_policies, :subject_item_policy
|
|
17
|
+
|
|
18
|
+
# @option [String] :basic_constraints
|
|
19
|
+
# @option [Array] :key_usage
|
|
20
|
+
# @option [Array] :extended_key_usage
|
|
21
|
+
# @option [Array] :certificate_policies
|
|
22
|
+
# @option [R509::Config::SubjectItemPolicy] :subject_item_policy optional
|
|
23
|
+
def initialize(opts = {})
|
|
24
|
+
@basic_constraints = opts[:basic_constraints]
|
|
25
|
+
@key_usage = opts[:key_usage]
|
|
26
|
+
@extended_key_usage = opts[:extended_key_usage]
|
|
27
|
+
@certificate_policies = opts[:certificate_policies]
|
|
28
|
+
if opts.has_key?(:subject_item_policy) and not opts[:subject_item_policy].kind_of?(R509::Config::SubjectItemPolicy)
|
|
29
|
+
end
|
|
30
|
+
@subject_item_policy = opts[:subject_item_policy] || nil
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# returns information about the subject item policy for a profile
|
|
35
|
+
class SubjectItemPolicy
|
|
36
|
+
attr_reader :required, :optional
|
|
37
|
+
|
|
38
|
+
# @param [Hash] hash of required/optional subject items. These must be in OpenSSL shortname format.
|
|
39
|
+
# @example sample hash
|
|
40
|
+
# {"CN" => "required",
|
|
41
|
+
# "O" => "required",
|
|
42
|
+
# "OU" => "optional",
|
|
43
|
+
# "ST" => "required",
|
|
44
|
+
# "C" => "required",
|
|
45
|
+
# "L" => "required",
|
|
46
|
+
# "emailAddress" => "optional"}
|
|
47
|
+
def initialize(hash={})
|
|
48
|
+
if not hash.kind_of?(Hash)
|
|
49
|
+
raise ArgumentError, "Must supply a hash in form 'shortname'=>'required/optional'"
|
|
50
|
+
end
|
|
51
|
+
@required = []
|
|
52
|
+
@optional = []
|
|
53
|
+
if not hash.empty?
|
|
54
|
+
hash.each_pair do |key,value|
|
|
55
|
+
if value == "required"
|
|
56
|
+
@required.push(key)
|
|
57
|
+
elsif value == "optional"
|
|
58
|
+
@optional.push(key)
|
|
59
|
+
else
|
|
60
|
+
raise ArgumentError, "Unknown subject item policy value. Allowed values are required and optional"
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# @param [R509::Subject] subject
|
|
67
|
+
# @return [R509::Subject] validated version of the subject or error
|
|
68
|
+
def validate_subject(subject)
|
|
69
|
+
# convert the subject components into an array of component names that match
|
|
70
|
+
# those that are on the required list
|
|
71
|
+
supplied = subject.to_a.each do |item|
|
|
72
|
+
@required.include?(item[0])
|
|
73
|
+
end.map do |item|
|
|
74
|
+
item[0]
|
|
75
|
+
end
|
|
76
|
+
# so we can make sure they gave us everything that's required
|
|
77
|
+
diff = @required - supplied
|
|
78
|
+
if not diff.empty?
|
|
79
|
+
raise R509::R509Error, "This profile requires you supply "+@required.join(", ")
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# the validated subject contains only those subject components that are either
|
|
83
|
+
# required or optional
|
|
84
|
+
R509::Subject.new(subject.to_a.select do |item|
|
|
85
|
+
@required.include?(item[0]) or @optional.include?(item[0])
|
|
86
|
+
end)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# pool of configs, so we can support multiple CAs from a single config file
|
|
91
|
+
class CaConfigPool
|
|
92
|
+
# @option configs [Hash<String, R509::Config::CaConfig>] the configs to add to the pool
|
|
93
|
+
def initialize(configs)
|
|
94
|
+
@configs = configs
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# get all the config names
|
|
98
|
+
def names
|
|
99
|
+
@configs.keys
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# retrieve a particular config by its name
|
|
103
|
+
def [](name)
|
|
104
|
+
@configs[name]
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# @return a list of all the configs in this pool
|
|
108
|
+
def all
|
|
109
|
+
@configs.values
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Loads the named configuration config from a yaml string.
|
|
113
|
+
# @param [String] name The name of the config within the file. Note
|
|
114
|
+
# that a single yaml file can contain more than one configuration.
|
|
115
|
+
# @param [String] yaml_data The filename to load yaml config data from.
|
|
116
|
+
def self.from_yaml(name, yaml_data, opts = {})
|
|
117
|
+
conf = YAML.load(yaml_data)
|
|
118
|
+
configs = {}
|
|
119
|
+
conf[name].each_pair do |ca_name, data|
|
|
120
|
+
configs[ca_name] = R509::Config::CaConfig.load_from_hash(data, opts)
|
|
121
|
+
end
|
|
122
|
+
R509::Config::CaConfigPool.new(configs)
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Stores a configuration for our CA.
|
|
127
|
+
class CaConfig
|
|
128
|
+
include R509::IOHelpers
|
|
129
|
+
extend R509::IOHelpers
|
|
130
|
+
attr_accessor :ca_cert, :crl_validity_hours, :message_digest,
|
|
131
|
+
:cdp_location, :crl_start_skew_seconds, :ocsp_location, :ocsp_chain,
|
|
132
|
+
:ocsp_start_skew_seconds, :ocsp_validity_hours, :crl_number_file, :crl_list_file
|
|
133
|
+
|
|
134
|
+
# @option opts [R509::Cert] :ca_cert Cert+Key pair
|
|
135
|
+
# @option opts [Integer] :crl_validity_hours (168) The number of hours that
|
|
136
|
+
# a CRL will be valid. Defaults to 7 days.
|
|
137
|
+
# @option opts [Hash<String, R509::Config::CaProfile>] :profiles
|
|
138
|
+
# @option opts [String] :message_digest (SHA1) The hashing algorithm to use.
|
|
139
|
+
# @option opts [String] :cdp_location
|
|
140
|
+
# @option opts [String] :ocsp_location
|
|
141
|
+
# @option opts [String] :crl_number_file The file that we will save
|
|
142
|
+
# the CRL numbers to. defaults to a StringIO object if not provided
|
|
143
|
+
# @option opts [String] :crl_list_file The file that we will save
|
|
144
|
+
# the CRL list data to. defaults to a StringIO object if not provided
|
|
145
|
+
# @option opts [R509::Cert] :ocsp_cert An optional cert+key pair
|
|
146
|
+
# OCSP signing delegate
|
|
147
|
+
# @option opts [Array<OpenSSL::X509::Certificate>] :ocsp_chain An optional array
|
|
148
|
+
# that constitutes the chain to attach to an OCSP response
|
|
149
|
+
#
|
|
150
|
+
def initialize(opts = {} )
|
|
151
|
+
if not opts.has_key?(:ca_cert) then
|
|
152
|
+
raise ArgumentError, 'Config object requires that you pass :ca_cert'
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
@ca_cert = opts[:ca_cert]
|
|
156
|
+
|
|
157
|
+
if not @ca_cert.kind_of?(R509::Cert) then
|
|
158
|
+
raise ArgumentError, ':ca_cert must be of type R509::Cert'
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
#ocsp data
|
|
162
|
+
if opts.has_key?(:ocsp_cert) and not opts[:ocsp_cert].kind_of?(R509::Cert) and not opts[:ocsp_cert].nil?
|
|
163
|
+
raise ArgumentError, ':ocsp_cert, if provided, must be of type R509::Cert'
|
|
164
|
+
end
|
|
165
|
+
if opts.has_key?(:ocsp_cert) and not opts[:ocsp_cert].nil? and not opts[:ocsp_cert].has_private_key?
|
|
166
|
+
raise ArgumentError, ':ocsp_cert must contain a private key, not just a certificate'
|
|
167
|
+
end
|
|
168
|
+
@ocsp_cert = opts[:ocsp_cert] unless opts[:ocsp_cert].nil?
|
|
169
|
+
@ocsp_location = opts[:ocsp_location]
|
|
170
|
+
@ocsp_chain = opts[:ocsp_chain] if opts[:ocsp_chain].kind_of?(Array)
|
|
171
|
+
@ocsp_validity_hours = opts[:ocsp_validity_hours] || 168
|
|
172
|
+
@ocsp_start_skew_seconds = opts[:ocsp_start_skew_seconds] || 3600
|
|
173
|
+
|
|
174
|
+
@crl_validity_hours = opts[:crl_validity_hours] || 168
|
|
175
|
+
@crl_start_skew_seconds = opts[:crl_start_skew_seconds] || 3600
|
|
176
|
+
@crl_number_file = opts[:crl_number_file] || nil
|
|
177
|
+
@crl_list_file = opts[:crl_list_file] || nil
|
|
178
|
+
@cdp_location = opts[:cdp_location]
|
|
179
|
+
@message_digest = opts[:message_digest] || "SHA1"
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
@profiles = {}
|
|
184
|
+
if opts[:profiles]
|
|
185
|
+
opts[:profiles].each_pair do |name, prof|
|
|
186
|
+
set_profile(name, prof)
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# @return [R509::Cert] either a custom OCSP cert or the ca_cert
|
|
193
|
+
def ocsp_cert
|
|
194
|
+
if @ocsp_cert.nil? then @ca_cert else @ocsp_cert end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# @param [String] name The name of the profile
|
|
198
|
+
# @param [R509::Config::CaProfile] prof The profile configuration
|
|
199
|
+
def set_profile(name, prof)
|
|
200
|
+
unless prof.is_a?(R509::Config::CaProfile)
|
|
201
|
+
raise TypeError, "profile is supposed to be a R509::Config::CaProfile"
|
|
202
|
+
end
|
|
203
|
+
@profiles[name] = prof
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
# @param [String] prof
|
|
207
|
+
# @return [R509::Config::CaProfile] The config profile.
|
|
208
|
+
def profile(prof)
|
|
209
|
+
if !@profiles.has_key?(prof)
|
|
210
|
+
raise R509::R509Error, "unknown profile '#{prof}'"
|
|
211
|
+
end
|
|
212
|
+
@profiles[prof]
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# @return [Integer] The number of profiles
|
|
216
|
+
def num_profiles
|
|
217
|
+
@profiles.count
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
######### Class Methods ##########
|
|
222
|
+
|
|
223
|
+
# Load the configuration from a data hash. The same type that might be
|
|
224
|
+
# used when loading from a YAML file.
|
|
225
|
+
# @param [Hash] conf A hash containing all the configuration options
|
|
226
|
+
# @option opts [String] :ca_root_path The root path for the CA. Defaults to
|
|
227
|
+
# the current working directory.
|
|
228
|
+
def self.load_from_hash(conf, opts = {})
|
|
229
|
+
if conf.nil?
|
|
230
|
+
raise ArgumentError, "conf not found"
|
|
231
|
+
end
|
|
232
|
+
unless conf.kind_of?(Hash)
|
|
233
|
+
raise ArgumentError, "conf must be a Hash"
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
ca_root_path = Pathname.new(opts[:ca_root_path] || FileUtils.getwd)
|
|
237
|
+
|
|
238
|
+
unless File.directory?(ca_root_path)
|
|
239
|
+
raise R509Error, "ca_root_path is not a directory: #{ca_root_path}"
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
ca_cert_hash = conf['ca_cert']
|
|
243
|
+
|
|
244
|
+
if ca_cert_hash.has_key?('engine')
|
|
245
|
+
ca_cert = self.load_with_engine(ca_cert_hash,ca_root_path)
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
if ca_cert.nil? and ca_cert_hash.has_key?('pkcs12')
|
|
249
|
+
ca_cert = self.load_with_pkcs12(ca_cert_hash,ca_root_path)
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
if ca_cert.nil? and ca_cert_hash.has_key?('cert')
|
|
253
|
+
ca_cert = self.load_with_key(ca_cert_hash,ca_root_path)
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
if conf.has_key?("ocsp_cert")
|
|
257
|
+
if conf["ocsp_cert"].has_key?('engine')
|
|
258
|
+
ocsp_cert = self.load_with_engine(conf["ocsp_cert"],ca_root_path)
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
if ocsp_cert.nil? and conf["ocsp_cert"].has_key?('pkcs12')
|
|
262
|
+
ocsp_cert = self.load_with_pkcs12(conf["ocsp_cert"],ca_root_path)
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
if ocsp_cert.nil? and conf["ocsp_cert"].has_key?('cert')
|
|
266
|
+
ocsp_cert = self.load_with_key(conf["ocsp_cert"],ca_root_path)
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
ocsp_chain = []
|
|
271
|
+
if conf.has_key?("ocsp_chain")
|
|
272
|
+
ocsp_chain_data = read_data(ca_root_path+conf["ocsp_chain"])
|
|
273
|
+
cert_regex = /-----BEGIN CERTIFICATE-----.+?-----END CERTIFICATE-----/m
|
|
274
|
+
ocsp_chain_data.scan(cert_regex) do |cert|
|
|
275
|
+
ocsp_chain.push(OpenSSL::X509::Certificate.new(cert))
|
|
276
|
+
end
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
opts = {
|
|
280
|
+
:ca_cert => ca_cert,
|
|
281
|
+
:ocsp_cert => ocsp_cert,
|
|
282
|
+
:ocsp_chain => ocsp_chain,
|
|
283
|
+
:crl_validity_hours => conf['crl_validity_hours'],
|
|
284
|
+
:ocsp_validity_hours => conf['ocsp_validity_hours'],
|
|
285
|
+
:ocsp_start_skew_seconds => conf['ocsp_start_skew_seconds'],
|
|
286
|
+
:ocsp_location => conf['ocsp_location'],
|
|
287
|
+
:cdp_location => conf['cdp_location'],
|
|
288
|
+
:message_digest => conf['message_digest'],
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if conf.has_key?("crl_list")
|
|
292
|
+
opts[:crl_list_file] = (ca_root_path + conf['crl_list']).to_s
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
if conf.has_key?("crl_number")
|
|
296
|
+
opts[:crl_number_file] = (ca_root_path + conf['crl_number']).to_s
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
profs = {}
|
|
301
|
+
conf['profiles'].keys.each do |profile|
|
|
302
|
+
data = conf['profiles'][profile]
|
|
303
|
+
if not data["subject_item_policy"].nil?
|
|
304
|
+
subject_item_policy = R509::Config::SubjectItemPolicy.new(data["subject_item_policy"])
|
|
305
|
+
end
|
|
306
|
+
profs[profile] = R509::Config::CaProfile.new(:key_usage => data["key_usage"],
|
|
307
|
+
:extended_key_usage => data["extended_key_usage"],
|
|
308
|
+
:basic_constraints => data["basic_constraints"],
|
|
309
|
+
:certificate_policies => data["certificate_policies"],
|
|
310
|
+
:subject_item_policy => subject_item_policy)
|
|
311
|
+
end unless conf['profiles'].nil?
|
|
312
|
+
opts[:profiles] = profs
|
|
313
|
+
|
|
314
|
+
# Create the instance.
|
|
315
|
+
self.new(opts)
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
# Loads the named configuration config from a yaml file.
|
|
319
|
+
# @param [String] conf_name The name of the config within the file. Note
|
|
320
|
+
# that a single yaml file can contain more than one configuration.
|
|
321
|
+
# @param [String] yaml_file The filename to load yaml config data from.
|
|
322
|
+
def self.load_yaml(conf_name, yaml_file, opts = {})
|
|
323
|
+
conf = YAML.load_file(yaml_file)
|
|
324
|
+
self.load_from_hash(conf[conf_name], opts)
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
# Loads the named configuration config from a yaml string.
|
|
328
|
+
# @param [String] conf_name The name of the config within the file. Note
|
|
329
|
+
# that a single yaml file can contain more than one configuration.
|
|
330
|
+
# @param [String] yaml_data The filename to load yaml config data from.
|
|
331
|
+
def self.from_yaml(conf_name, yaml_data, opts = {})
|
|
332
|
+
conf = YAML.load(yaml_data)
|
|
333
|
+
self.load_from_hash(conf[conf_name], opts)
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
private
|
|
337
|
+
|
|
338
|
+
def self.load_with_engine(ca_cert_hash,ca_root_path)
|
|
339
|
+
if ca_cert_hash.has_key?('key')
|
|
340
|
+
raise R509Error, "You can't specify both key and engine"
|
|
341
|
+
end
|
|
342
|
+
if ca_cert_hash.has_key?('pkcs12')
|
|
343
|
+
raise R509Error, "You can't specify both engine and pkcs12"
|
|
344
|
+
end
|
|
345
|
+
if not ca_cert_hash.has_key?('key_name')
|
|
346
|
+
raise R509Error, "You must supply a key_name with an engine"
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
if ca_cert_hash['engine'].respond_to?(:load_private_key)
|
|
350
|
+
#this path is only for testing...ugh
|
|
351
|
+
engine = ca_cert_hash['engine']
|
|
352
|
+
else
|
|
353
|
+
#this path can't be tested by unit tests. bah!
|
|
354
|
+
engine = OpenSSL::Engine.by_id(ca_cert_hash['engine'])
|
|
355
|
+
end
|
|
356
|
+
ca_key = R509::PrivateKey.new(
|
|
357
|
+
:engine => engine,
|
|
358
|
+
:key_name => ca_cert_hash['key_name']
|
|
359
|
+
)
|
|
360
|
+
ca_cert_file = ca_root_path + ca_cert_hash['cert']
|
|
361
|
+
ca_cert = R509::Cert.new(
|
|
362
|
+
:cert => read_data(ca_cert_file),
|
|
363
|
+
:key => ca_key
|
|
364
|
+
)
|
|
365
|
+
ca_cert
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
def self.load_with_pkcs12(ca_cert_hash,ca_root_path)
|
|
369
|
+
if ca_cert_hash.has_key?('cert')
|
|
370
|
+
raise R509Error, "You can't specify both pkcs12 and cert"
|
|
371
|
+
end
|
|
372
|
+
if ca_cert_hash.has_key?('key')
|
|
373
|
+
raise R509Error, "You can't specify both pkcs12 and key"
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
pkcs12_file = ca_root_path + ca_cert_hash['pkcs12']
|
|
377
|
+
ca_cert = R509::Cert.new(
|
|
378
|
+
:pkcs12 => read_data(pkcs12_file),
|
|
379
|
+
:password => ca_cert_hash['password']
|
|
380
|
+
)
|
|
381
|
+
ca_cert
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
def self.load_with_key(ca_cert_hash,ca_root_path)
|
|
385
|
+
ca_cert_file = ca_root_path + ca_cert_hash['cert']
|
|
386
|
+
|
|
387
|
+
if ca_cert_hash.has_key?('key')
|
|
388
|
+
ca_key_file = ca_root_path + ca_cert_hash['key']
|
|
389
|
+
ca_key = R509::PrivateKey.new(
|
|
390
|
+
:key => read_data(ca_key_file),
|
|
391
|
+
:password => ca_cert_hash['password']
|
|
392
|
+
)
|
|
393
|
+
ca_cert = R509::Cert.new(
|
|
394
|
+
:cert => read_data(ca_cert_file),
|
|
395
|
+
:key => ca_key
|
|
396
|
+
)
|
|
397
|
+
else
|
|
398
|
+
# in certain cases (OCSP responders for example) we may want
|
|
399
|
+
# to load a ca_cert with no private key
|
|
400
|
+
ca_cert = R509::Cert.new(:cert => read_data(ca_cert_file))
|
|
401
|
+
end
|
|
402
|
+
ca_cert
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
end
|
|
406
|
+
end
|
|
407
|
+
end
|
data/lib/r509/crl.rb
ADDED
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
require 'openssl'
|
|
2
|
+
require 'r509/config'
|
|
3
|
+
require 'r509/exceptions'
|
|
4
|
+
require 'r509/io_helpers'
|
|
5
|
+
|
|
6
|
+
module R509
|
|
7
|
+
# contains CRL related classes (generator and a pre-existing list loader)
|
|
8
|
+
module Crl
|
|
9
|
+
|
|
10
|
+
class Parser
|
|
11
|
+
attr_reader :crl
|
|
12
|
+
|
|
13
|
+
# @param [String,OpenSSL::X509::CRL] crl
|
|
14
|
+
def initialize(crl)
|
|
15
|
+
@crl = OpenSSL::X509::CRL.new(crl)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Helper method to quickly load a CRL from the filesystem
|
|
19
|
+
#
|
|
20
|
+
# @param [String] filename Path to file you want to load
|
|
21
|
+
# @return [R509::Crl::Parser] CRL object
|
|
22
|
+
def self.load_from_file( filename )
|
|
23
|
+
return R509::Crl::Parser.new( IOHelpers.read_data(filename) )
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# @return [OpenSSL::X509::Name]
|
|
27
|
+
def issuer
|
|
28
|
+
@crl.issuer
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# @return [String] The common name (CN) component of the issuer
|
|
32
|
+
def issuer_cn
|
|
33
|
+
return nil if self.issuer.nil?
|
|
34
|
+
|
|
35
|
+
self.issuer.to_a.each do |part, value, length|
|
|
36
|
+
return value if part.upcase == 'CN'
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# return nil if we didn't find a CN part
|
|
40
|
+
return nil
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# @return [Time]
|
|
44
|
+
def last_update
|
|
45
|
+
@crl.last_update
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# @return [Time]
|
|
49
|
+
def next_update
|
|
50
|
+
@crl.next_update
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# @return [String]
|
|
54
|
+
def signature_algorithm
|
|
55
|
+
@crl.signature_algorithm
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Pass a public key to verify that the CRL is signed by a specific certificate (call cert.public_key on that object)
|
|
59
|
+
#
|
|
60
|
+
# @param [OpenSSL::PKey::PKey] public_key
|
|
61
|
+
# @return [Boolean]
|
|
62
|
+
def verify(public_key)
|
|
63
|
+
@crl.verify(public_key)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# @param [Integer] serial number
|
|
67
|
+
# @return [Boolean]
|
|
68
|
+
def revoked?(serial)
|
|
69
|
+
if @crl.revoked.find { |revoked| revoked.serial == serial }
|
|
70
|
+
true
|
|
71
|
+
else
|
|
72
|
+
false
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# @return [Hash] hash of serial => { :time, :reason } hashes
|
|
77
|
+
def revoked
|
|
78
|
+
revoked_list = {}
|
|
79
|
+
@crl.revoked.each do |revoked|
|
|
80
|
+
reason = get_reason(revoked)
|
|
81
|
+
revoked_list[revoked.serial.to_i] = { :time => revoked.time, :reason => reason }
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
revoked_list
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# @param [Integer] serial number
|
|
88
|
+
# @return [Hash] hash with :time and :reason
|
|
89
|
+
def revoked_cert(serial)
|
|
90
|
+
revoked = @crl.revoked.find { |revoked| revoked.serial == serial }
|
|
91
|
+
if revoked
|
|
92
|
+
reason = get_reason(revoked)
|
|
93
|
+
{ :time => revoked.time, :reason => reason }
|
|
94
|
+
else
|
|
95
|
+
nil
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
private
|
|
100
|
+
def get_reason(revocation_object)
|
|
101
|
+
reason = nil
|
|
102
|
+
revocation_object.extensions.each do |extension|
|
|
103
|
+
if extension.oid == "CRLReason"
|
|
104
|
+
reason = extension.value
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
reason
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Used to manage revocations and generate CRLs
|
|
113
|
+
class Administrator
|
|
114
|
+
include R509::IOHelpers
|
|
115
|
+
|
|
116
|
+
attr_reader :crl_number,:crl_list_file,:crl_number_file, :validity_hours
|
|
117
|
+
|
|
118
|
+
# @param [R509::Config::CaConfig] config
|
|
119
|
+
def initialize(config)
|
|
120
|
+
@config = config
|
|
121
|
+
|
|
122
|
+
unless @config.kind_of?(R509::Config::CaConfig)
|
|
123
|
+
raise R509Error, "config must be a kind of R509::Config::CaConfig"
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
@validity_hours = @config.crl_validity_hours
|
|
127
|
+
@start_skew_seconds = @config.crl_start_skew_seconds
|
|
128
|
+
@crl = nil
|
|
129
|
+
|
|
130
|
+
@crl_number_file = @config.crl_number_file
|
|
131
|
+
if not @crl_number_file.nil?
|
|
132
|
+
@crl_number = read_data(@crl_number_file).to_i
|
|
133
|
+
else
|
|
134
|
+
@crl_number = 0
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
@crl_list_file = @config.crl_list_file
|
|
139
|
+
load_crl_list(@crl_list_file)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Indicates whether the serial number has been revoked, or not.
|
|
143
|
+
#
|
|
144
|
+
# @param [Integer] serial The serial number we want to check
|
|
145
|
+
# @return [Boolean] True if the serial number was revoked. False, otherwise.
|
|
146
|
+
def revoked?(serial)
|
|
147
|
+
@revoked_certs.has_key?(serial)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# @return [Array] serial, reason, revoke_time tuple
|
|
151
|
+
def revoked_cert(serial)
|
|
152
|
+
@revoked_certs[serial]
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Returns the CRL in PEM format
|
|
156
|
+
#
|
|
157
|
+
# @return [String] the CRL in PEM format
|
|
158
|
+
def to_pem
|
|
159
|
+
@crl.to_pem
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
alias :to_s :to_pem
|
|
163
|
+
|
|
164
|
+
# Returns the CRL in DER format
|
|
165
|
+
#
|
|
166
|
+
# @return [String] the CRL in DER format
|
|
167
|
+
def to_der
|
|
168
|
+
@crl.to_der
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# @return [R509::Crl::Parser]
|
|
172
|
+
def to_crl
|
|
173
|
+
return nil if @crl.nil?
|
|
174
|
+
return R509::Crl::Parser.new(@crl)
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Writes the CRL into the PEM format
|
|
178
|
+
#
|
|
179
|
+
# @param [String, #write] filename_or_io Either a string of the path for
|
|
180
|
+
# the file that you'd like to write, or an IO-like object.
|
|
181
|
+
def write_pem(filename_or_io)
|
|
182
|
+
write_data(filename_or_io, @crl.to_pem)
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# Writes the CRL into the PEM format
|
|
186
|
+
#
|
|
187
|
+
# @param [String, #write] filename_or_io Either a string of the path for
|
|
188
|
+
# the file that you'd like to write, or an IO-like object.
|
|
189
|
+
def write_der(filename_or_io)
|
|
190
|
+
write_data(filename_or_io, @crl.to_der)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# Returns the signing time of the CRL
|
|
194
|
+
#
|
|
195
|
+
# @return [Time] when the CRL was signed
|
|
196
|
+
def last_update
|
|
197
|
+
@crl.last_update
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# Returns the next update time for the CRL
|
|
201
|
+
#
|
|
202
|
+
# @return [Time] when it will be updated next
|
|
203
|
+
def next_update
|
|
204
|
+
@crl.next_update
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# Adds a certificate to the revocation list. After calling you must call generate_crl to sign a new CRL
|
|
208
|
+
#
|
|
209
|
+
# @param serial [Integer] serial number of the certificate to revoke
|
|
210
|
+
# @param reason [Integer] reason for revocation
|
|
211
|
+
# @param revoke_time [Integer]
|
|
212
|
+
# @param generate_and_save [Boolean] whether we want to generate the CRL and save its file (default=true)
|
|
213
|
+
#
|
|
214
|
+
# reason codes defined by rfc 5280
|
|
215
|
+
#
|
|
216
|
+
# CRLReason ::= ENUMERATED {
|
|
217
|
+
# unspecified (0),
|
|
218
|
+
# keyCompromise (1),
|
|
219
|
+
# cACompromise (2),
|
|
220
|
+
# affiliationChanged (3),
|
|
221
|
+
# superseded (4),
|
|
222
|
+
# cessationOfOperation (5),
|
|
223
|
+
# certificateHold (6),
|
|
224
|
+
# removeFromCRL (8),
|
|
225
|
+
# privilegeWithdrawn (9),
|
|
226
|
+
# aACompromise (10) }
|
|
227
|
+
def revoke_cert(serial,reason=nil, revoke_time=Time.now.to_i, generate_and_save=true)
|
|
228
|
+
if not reason.to_i.between?(0,10)
|
|
229
|
+
reason = 0
|
|
230
|
+
end
|
|
231
|
+
serial = serial.to_i
|
|
232
|
+
reason = reason.to_i
|
|
233
|
+
revoke_time = revoke_time.to_i
|
|
234
|
+
if revoked?(serial)
|
|
235
|
+
raise R509::R509Error, "Cannot revoke a previously revoked certificate"
|
|
236
|
+
end
|
|
237
|
+
@revoked_certs[serial] = {:reason => reason, :revoke_time => revoke_time}
|
|
238
|
+
if generate_and_save
|
|
239
|
+
generate_crl()
|
|
240
|
+
save_crl_list()
|
|
241
|
+
end
|
|
242
|
+
nil
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
# Remove serial from revocation list. After unrevoking you must call generate_crl to sign a new CRL
|
|
246
|
+
#
|
|
247
|
+
# @param serial [Integer] serial number of the certificate to remove from revocation
|
|
248
|
+
def unrevoke_cert(serial)
|
|
249
|
+
@revoked_certs.delete(serial)
|
|
250
|
+
generate_crl()
|
|
251
|
+
save_crl_list()
|
|
252
|
+
nil
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
# Remove serial from revocation list
|
|
256
|
+
#
|
|
257
|
+
# @return [String] PEM encoded signed CRL
|
|
258
|
+
def generate_crl
|
|
259
|
+
crl = OpenSSL::X509::CRL.new
|
|
260
|
+
crl.version = 1
|
|
261
|
+
now = Time.at Time.now.to_i
|
|
262
|
+
crl.last_update = now-@start_skew_seconds
|
|
263
|
+
crl.next_update = now+@validity_hours*3600
|
|
264
|
+
crl.issuer = @config.ca_cert.subject
|
|
265
|
+
|
|
266
|
+
self.revoked_certs.each do |serial, reason, revoke_time|
|
|
267
|
+
revoked = OpenSSL::X509::Revoked.new
|
|
268
|
+
revoked.serial = OpenSSL::BN.new serial.to_s
|
|
269
|
+
revoked.time = Time.at(revoke_time)
|
|
270
|
+
if !reason.nil?
|
|
271
|
+
enum = OpenSSL::ASN1::Enumerated(reason) #see reason codes below
|
|
272
|
+
ext = OpenSSL::X509::Extension.new("CRLReason", enum)
|
|
273
|
+
revoked.add_extension(ext)
|
|
274
|
+
end
|
|
275
|
+
#now add it to the crl
|
|
276
|
+
crl.add_revoked(revoked)
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
ef = OpenSSL::X509::ExtensionFactory.new
|
|
280
|
+
ef.issuer_certificate = @config.ca_cert.cert
|
|
281
|
+
ef.crl = crl
|
|
282
|
+
#grab crl number from file, increment, write back
|
|
283
|
+
crl_number = increment_crl_number
|
|
284
|
+
crlnum = OpenSSL::ASN1::Integer(crl_number)
|
|
285
|
+
crl.add_extension(OpenSSL::X509::Extension.new("crlNumber", crlnum))
|
|
286
|
+
extensions = []
|
|
287
|
+
extensions << ["authorityKeyIdentifier", "keyid:always,issuer:always", false]
|
|
288
|
+
extensions.each{|oid, value, critical|
|
|
289
|
+
crl.add_extension(ef.create_extension(oid, value, critical))
|
|
290
|
+
}
|
|
291
|
+
crl.sign(@config.ca_cert.key.key, OpenSSL::Digest::SHA1.new)
|
|
292
|
+
@crl = crl
|
|
293
|
+
@crl.to_pem
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
# @return [Array<Array>] Returns an array of serial, reason, revoke_time
|
|
297
|
+
# tuples.
|
|
298
|
+
def revoked_certs
|
|
299
|
+
ret = []
|
|
300
|
+
@revoked_certs.keys.sort.each do |serial|
|
|
301
|
+
ret << [serial, @revoked_certs[serial][:reason], @revoked_certs[serial][:revoke_time]]
|
|
302
|
+
end
|
|
303
|
+
ret
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
# Saves the CRL list to a filename or IO. If the class was initialized
|
|
307
|
+
# with :crl_list_file, then the filename specified by that will be used
|
|
308
|
+
# by default.
|
|
309
|
+
# @param [String, #write, nil] filename_or_io If provided, the generated
|
|
310
|
+
# crl will be written to either the file (if a string), or IO. If nil,
|
|
311
|
+
# then the @crl_list_file will be used. If that is nil, then an error
|
|
312
|
+
# will be raised.
|
|
313
|
+
def save_crl_list(filename_or_io = @crl_list_file)
|
|
314
|
+
return nil if filename_or_io.nil?
|
|
315
|
+
|
|
316
|
+
data = []
|
|
317
|
+
self.revoked_certs.each do |serial, reason, revoke_time|
|
|
318
|
+
data << [serial, revoke_time, reason].join(',')
|
|
319
|
+
end
|
|
320
|
+
write_data(filename_or_io, data.join("\n"))
|
|
321
|
+
nil
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
# Save the CRL number to a filename or IO. If the class was initialized
|
|
325
|
+
# with :crl_number_file, then the filename specified by that will be used
|
|
326
|
+
# by default.
|
|
327
|
+
# @param [String, #write, nil] filename_or_io If provided, the current
|
|
328
|
+
# crl number will be written to either the file (if a string), or IO. If nil,
|
|
329
|
+
# then the @crl_number_file will be used. If that is nil, then an error
|
|
330
|
+
# will be raised.
|
|
331
|
+
def save_crl_number(filename_or_io = @crl_number_file)
|
|
332
|
+
return nil if filename_or_io.nil?
|
|
333
|
+
# No valid filename or IO was specified, so bail.
|
|
334
|
+
|
|
335
|
+
write_data(filename_or_io, self.crl_number.to_s)
|
|
336
|
+
nil
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
private
|
|
340
|
+
|
|
341
|
+
# Increments the crl_number.
|
|
342
|
+
# @return [Integer] the new CRL number
|
|
343
|
+
#
|
|
344
|
+
def increment_crl_number
|
|
345
|
+
@crl_number += 1
|
|
346
|
+
save_crl_number()
|
|
347
|
+
@crl_number
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
# Loads the certificate revocation list from file.
|
|
351
|
+
# @param [String, #read, nil] filename_or_io The
|
|
352
|
+
# crl will be read from either the file (if a string), or IO.
|
|
353
|
+
def load_crl_list(filename_or_io)
|
|
354
|
+
@revoked_certs = {}
|
|
355
|
+
|
|
356
|
+
if filename_or_io.nil?
|
|
357
|
+
generate_crl
|
|
358
|
+
return nil
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
data = read_data(filename_or_io)
|
|
362
|
+
|
|
363
|
+
data.each_line do |line|
|
|
364
|
+
line.chomp!
|
|
365
|
+
serial, revoke_time, reason = line.split(',', 3)
|
|
366
|
+
serial = serial.to_i
|
|
367
|
+
reason = (reason == '') ? nil : reason.to_i
|
|
368
|
+
revoke_time = (revoke_time == '') ? nil : revoke_time.to_i
|
|
369
|
+
self.revoke_cert(serial, reason, revoke_time, false)
|
|
370
|
+
end
|
|
371
|
+
generate_crl
|
|
372
|
+
save_crl_list
|
|
373
|
+
nil
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
end
|
|
377
|
+
end
|
|
378
|
+
end
|
|
379
|
+
|