-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathgen-csr.rb
153 lines (123 loc) · 3.85 KB
/
gen-csr.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
#!/usr/bin/env ruby
require 'openssl'
require 'optparse'
require 'ostruct'
require 'yaml'
require 'pathname'
require 'pp'
options = OpenStruct.new
options.names = []
options.ips = []
options.cfg = "/etc/gen-csr.conf"
options.key_size = 2048
options.email = nil
options.output_path = "~/csr"
options.add_www = false
options.stdout = false
TIMESTAMP = Time.now.strftime("%FT%R")
def exit_error(msg)
STDERR.puts "Error: #{msg}"
exit 1
end
OptionParser.new do |opts|
opts.banner = "Usage: gen-csr.rb [options]"
opts.on('-c', '--config CONFIG', "Load defaults from specified YAML config-file. (Default: /etc/gen-csr.conf)") do |opt|
options.cfg = opt
end
opts.on('-n', '--name DNSNAME', 'DNS-names to add to certificate, first will also be used for common-name') do |opt|
options.names << opt
end
opts.on('-s', '--keysize SIZE', Integer, 'Keysize for private key (Default: 2048)') do |opt|
options.key_size = opt
end
opts.on('-o', '--output-path DIRECTORY', 'Path to write certificate + key (Default: ~/csr)') do |opt|
options.output_path = opt
end
opts.on('-w', '--add-www', 'Prefix www.* to names') do |opt|
options.add_www = true
end
opts.on('-i', '--ip IPADDR', 'IP-Address to add in certificate') do |opt|
options.ips << opt
end
opts.on('--stdout', 'Print output to stderr instead of files') do |opt|
options.stdout = true
end
end.parse!
# read config for C, ST, L, ETC
begin
loaded_config = YAML.load_file(options.cfg)
#pp loaded_config
['country', 'state', 'locality', 'organization', 'orgunit'].each do |k|
raise ArgumentError, "Missing option '#{k}' in config-file ('#{options.cfg}')" unless loaded_config.key?(k)
options[k] = loaded_config[k]
end
# Optional email
options.email = loaded_config['email'] if loaded_config.key?('email')
rescue Errno::ENOENT
exit_error "Config-file '#{options.cfg}' does not exist. Specify a config-file with -c option."
rescue ArgumentError => e
exit_error e
end
# any dns names provided?
if options.names.size == 0
exit_error "Option '--name DNSNAME' must be provided at least once"
end
# set common_name to first name
options.common_name = options.names.first
# Add www. if requested
options.names << options.names.reject {|i| i.start_with?('www.') }.collect {|i| "www.#{i}" } if options.add_www
# check output directory
begin
output_path = Pathname.new(File.expand_path options.output_path)
if output_path.exist?
exit_error "Output-path points to a file" unless output_path.directory?
else
output_path.mkpath
end
output_path.chmod(0750)
rescue Errno::EACCES => e
exit_error "Could not setup output-directory: #{e}"
end
unless options.stdout
puts "Configured options:"
pp options.to_h
end
key = OpenSSL::PKey::RSA.new 2048
key_file = output_path + "#{options.common_name}-#{TIMESTAMP}.key"
if options.stdout
STDOUT.puts(key)
else
key_file.open('w') {|f| f.write(key) }
puts "Wrote key: #{key_file}"
end
request_opts = [
['C', options.country],
['CN', options.common_name],
['O', options.organization],
['OU', options.orgunit],
['ST', options.state],
['L', options.locality],
]
request_opts << ['emailAddress', options.email] if options.email
csr = OpenSSL::X509::Request.new
csr.version = 0
csr.subject = OpenSSL::X509::Name.new(request_opts)
csr.public_key = key.public_key
# add additional dns-names?
extension = OpenSSL::X509::ExtensionFactory.new.create_extension(
'subjectAltName',
(options.names.map {|san| "DNS:#{san}"} + options.ips.map {|ip| "IP:#{ip}"}).join(', '),
false
)
csr.add_attribute OpenSSL::X509::Attribute.new(
'extReq',
OpenSSL::ASN1::Set.new( [OpenSSL::ASN1::Sequence([extension])] )
)
csr.sign(key, OpenSSL::Digest::SHA256.new)
csr_file = output_path + "#{options.common_name}-#{TIMESTAMP}.csr"
if options.stdout
STDOUT.puts csr
else
csr_file.open('w') {|f| f.write(csr) }
puts "Wrote CSR: #{csr_file}"
end