-
Notifications
You must be signed in to change notification settings - Fork 2
/
objects.rb
359 lines (325 loc) · 11.9 KB
/
objects.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
require 'openssl'
require "logger"
LOG = Logger.new(STDOUT)
OpenSSL::PKey::EC.send(:alias_method, :private?, :private_key?)
module Vcert
SUPPORTED_CURVES = ["secp224r1", "prime256v1", "secp521r1"]
class Request
attr_accessor :id, :thumbprint
attr_reader :common_name, :country, :province, :locality, :organization, :organizational_unit, :san_dns,:key_type
def initialize(common_name: nil, private_key: nil, key_type: nil,
organization: nil, organizational_unit: nil, country: nil, province: nil, locality: nil, san_dns: nil,
friendly_name: nil, csr: nil)
@common_name = common_name
@private_key = private_key
#todo: parse private key and set public
if key_type != nil && !key_type.instance_of?(KeyType)
raise Vcert::ClientBadDataError, "key_type bad type. should be Vcert::KeyType. for example KeyType('rsa', 2048)"
end
@key_type = key_type
@organization = organization
@organizational_unit = organizational_unit
@country = country
@province = province
@locality = locality
@san_dns = san_dns
@friendly_name = friendly_name
@id = nil
@csr = csr
end
def generate_csr
if @private_key == nil
generate_private_key
end
subject_attrs = [
['CN', @common_name]
]
if @organization != nil
subject_attrs.push(['O', @organization])
end
if @organizational_unit != nil
if @organizational_unit.kind_of?(Array)
@organizational_unit.each { |ou| subject_attrs.push(['OU', ou]) }
else
subject_attrs.push(['OU', @organizational_unit])
end
end
if @country != nil
subject_attrs.push(['C', @country])
end
if @province != nil
subject_attrs.push(['ST', @province])
end
if @locality != nil
subject_attrs.push(['L', @locality])
end
LOG.info("Making request from subject array #{subject_attrs.inspect}")
subject = OpenSSL::X509::Name.new subject_attrs
csr = OpenSSL::X509::Request.new
csr.version = 0
csr.subject = subject
csr.public_key = @public_key
if @san_dns != nil
unless @san_dns.kind_of?(Array)
@san_dns = [@san_dns]
end
#TODO: add check that san_dns is an array
san_list = @san_dns.map { |domain| "DNS:#{domain}" }
extensions = [
OpenSSL::X509::ExtensionFactory.new.create_extension('subjectAltName', san_list.join(','))
]
attribute_values = OpenSSL::ASN1::Set [OpenSSL::ASN1::Sequence(extensions)]
[
OpenSSL::X509::Attribute.new('extReq', attribute_values),
OpenSSL::X509::Attribute.new('msExtReq', attribute_values)
].each do |attribute|
csr.add_attribute attribute
end
end
csr.sign @private_key, OpenSSL::Digest::SHA256.new # todo: changable sign alg
@csr = csr.to_pem
end
def csr
# TODO: find a way to pass CSR generation if renew is requested
if @csr == nil
generate_csr
end
@csr
end
def csr?
@csr != nil
end
def private_key
if @private_key == nil
generate_private_key
end
@private_key.to_pem
end
def friendly_name
if @friendly_name != nil
return @friendly_name
end
@common_name
end
# @param [ZoneConfiguration] zone_config
def update_from_zone_config(zone_config)
if zone_config.country.locked || (!@country && !!zone_config.country.value)
@country = zone_config.country.value
end
if zone_config.locality.locked || (!@locality && !!zone_config.locality.value)
@locality = zone_config.locality.value
end
if zone_config.province.locked || (!@province && !!zone_config.province.value)
@province = zone_config.province.value
end
if zone_config.organization.locked || (!@organization && !!zone_config.organization.value)
@organization = zone_config.organization.value
end
if zone_config.organizational_unit.locked || (!@organizational_unit && !!zone_config.organizational_unit.value)
@organizational_unit = zone_config.organizational_unit.value
end
if zone_config.key_type.locked || (@key_type == nil && zone_config.key_type.value != nil)
@key_type = zone_config.key_type.value
end
end
private
def generate_private_key
if @key_type == nil
@key_type = DEFAULT_KEY_TYPE
end
if @key_type.type == "rsa"
@private_key = OpenSSL::PKey::RSA.new @key_type.option
@public_key = @private_key.public_key
elsif @key_type.type == "ecdsa"
@private_key, @public_key = OpenSSL::PKey::EC.new(@key_type.option), OpenSSL::PKey::EC.new(@key_type.option)
@private_key.generate_key
@public_key.public_key = @private_key.public_key
end
end
end
class Certificate
attr_accessor :private_key
attr_reader :cert, :chain
def initialize(cert: nil, chain: nil, private_key: nil)
@cert = cert
@chain = chain
@private_key = private_key
end
end
class Policy
attr_reader :policy_id, :name, :system_generated, :creation_date
def initialize(policy_id:, name:, system_generated:, creation_date:, subject_cn_regexes:, subject_o_regexes:,
subject_ou_regexes:, subject_st_regexes:, subject_l_regexes:, subject_c_regexes:, san_regexes:,
key_types:)
@policy_id = policy_id
@name = name
@system_generated = system_generated
@creation_date = creation_date
@subject_cn_regexes = subject_cn_regexes
@subject_c_regexes = subject_c_regexes
@subject_st_regexes = subject_st_regexes
@subject_l_regexes = subject_l_regexes
@subject_o_regexes = subject_o_regexes
@subject_ou_regexes = subject_ou_regexes
@san_regexes = san_regexes
@key_types = key_types
end
# @param [Request] request
def simple_check_request(request)
if request.csr?
csr = parse_csr_fields(request.csr)
unless component_is_valid?(csr[:CN], @subject_cn_regexes)
raise ValidationError, "Common name #{csr[:CN]} doesnt match #{@subject_cn_regexes}"
end
unless component_is_valid?(request.san_dns, @san_regexes, optional: true)
raise ValidationError, "SANs #{csr[:DNS]} doesnt match #{ @san_regexes }"
end
else
unless component_is_valid?(request.common_name, @subject_cn_regexes)
raise ValidationError, "Common name #{request.common_name} doesnt match #{@subject_cn_regexes}"
end
unless component_is_valid?(request.san_dns, @san_regexes, optional: true)
raise ValidationError, "SANs #{request.san_dns} doesnt match #{ @san_regexes }"
end
end
end
# @param [Request] request
def check_request(request)
simple_check_request(request)
if request.csr?
csr = parse_csr_fields(request.csr)
unless component_is_valid?(csr[:C], @subject_c_regexes)
raise ValidationError, "Country #{csr[:C]} doesnt match #{@subject_c_regexes}"
end
unless component_is_valid?(csr[:ST], @subject_st_regexes)
raise ValidationError, "Province #{csr[:ST]} doesnt match #{@subject_st_regexes}"
end
unless component_is_valid?(csr[:L], @subject_l_regexes)
raise ValidationError, "Locality #{csr[:L]} doesnt match #{@subject_l_regexes}"
end
unless component_is_valid?(csr[:O], @subject_o_regexes)
raise ValidationError, "Organization #{csr[:O]} doesnt match #{@subject_o_regexes}"
end
unless component_is_valid?(csr[:OU], @subject_ou_regexes)
raise ValidationError, "Organizational unit #{csr[:OU]} doesnt match #{@subject_ou_regexes}"
end
#todo: add uri, upn, ip, email
unless is_key_type_is_valid?(csr[:key_type], @key_types)
raise ValidationError, "Key Type #{csr[:key_type]} doesnt match allowed #{@key_types}"
end
else
# subject
unless component_is_valid?(request.country, @subject_c_regexes)
raise ValidationError, "Country #{request.country} doesnt match #{@subject_c_regexes}"
end
unless component_is_valid?(request.province, @subject_st_regexes)
raise ValidationError, "Province #{request.province} doesnt match #{@subject_st_regexes}"
end
unless component_is_valid?(request.locality, @subject_l_regexes)
raise ValidationError, "Locality #{request.locality} doesnt match #{@subject_l_regexes}"
end
unless component_is_valid?(request.organization, @subject_o_regexes)
raise ValidationError, "Organization #{request.organization} doesnt match #{@subject_o_regexes}"
end
unless component_is_valid?(request.organizational_unit, @subject_ou_regexes)
raise ValidationError, "Organizational unit #{request.organizational_unit} doesnt match #{@subject_ou_regexes}"
end
#todo: add uri, upn, ip, email
unless is_key_type_is_valid?(request.key_type, @key_types)
raise ValidationError, "Key Type #{request.key_type} doesnt match allowed #{@key_types}"
end
end
# todo: (!important!) parse csr if it alredy generated (!important!)
end
private
def is_key_type_is_valid?(key_type, allowed_key_types)
if key_type == nil
key_type = DEFAULT_KEY_TYPE
end
for i in 0 ... allowed_key_types.length
if allowed_key_types[i] == key_type
return true
end
end
false
end
def component_is_valid?(component, regexps, optional:false)
if component == nil
component = []
end
unless component.instance_of? Array
component = [component]
end
if component.length == 0 && optional
return true
end
if component.length == 0
component = [""]
end
for i in 0 ... component.length
unless match_regexps?(component[i], regexps)
return false
end
end
true
end
def match_regexps?(s, regexps)
for i in 0 ... regexps.length
if Regexp.new(regexps[i]).match(s)
return true
end
end
false
end
end
class ZoneConfiguration
attr_reader :country, :province, :locality, :organization, :organizational_unit, :key_type
# @param [CertField] country
# @param [CertField] province
# @param [CertField] locality
# @param [CertField] organization
# @param [CertField] organizational_unit
# @param [CertField] key_type
def initialize(country:, province:, locality:, organization:, organizational_unit:, key_type:)
@country = country
@province = province
@locality = locality
@organization = organization
@organizational_unit = organizational_unit
@key_type = key_type
end
end
class CertField
attr_reader :value, :locked
def initialize(value, locked: false)
@value = value
@locked = locked
end
end
class KeyType
attr_reader :type, :option
def initialize(type, option)
@type = {"rsa" => "rsa", "ec" => "ecdsa", "ecdsa" => "ecdsa"}[type.downcase]
if @type == nil
raise Vcert::VcertError, "bad key type"
end
if @type == "rsa"
unless [512, 1024, 2048, 3072, 4096, 8192].include?(option)
raise Vcert::VcertError,"bad option for rsa key: #{option}. should be one from list 512, 1024, 2048, 3072, 4096, 8192"
end
else
unless SUPPORTED_CURVES.include?(option)
raise Vcert::VcertError, "bad option for ec key: #{option}. should be one from list #{ SUPPORTED_CURVES}"
end
end
@option = option
end
def ==(other)
unless other.instance_of? KeyType
return false
end
self.type == other.type && self.option == other.option
end
end
DEFAULT_KEY_TYPE = KeyType.new("rsa", 2048)
end