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
|
class Puppet::SSLCertificates::CA
Certificate = Puppet::SSLCertificates::Certificate
attr_accessor :keyfile, :file, :config, :dir, :cert
Puppet.setdefaults("ca",
:certdir => ["$ssldir/certs", "The certificate directory."],
:publickeydir => ["$ssldir/public_keys", "The public key directory."],
:privatekeydir => ["$ssldir/private_keys", "The private key directory."],
:cadir => ["$ssldir/ca",
"The root directory for the certificate authority."],
:cacert => ["$cadir/ca_crt.pem", "The CA certificate."],
:cakey => ["$cadir/ca_key.pem", "The CA private key."],
:capub => ["$cadir/ca_pub.pem", "The CA public key."],
:caprivatedir => ["$cadir/private",
"Where the CA stores private certificate information."],
:csrdir => ["$cadir/requests",
"Where the CA stores certificate requests"],
:signeddir => ["$cadir/signed",
"Where the CA stores signed certificates."],
:capass => ["$caprivatedir/ca.pass",
"Where the CA stores the password for the private key"],
:serial => ["$cadir/serial",
"Where the serial number for certificates is stored."],
:privatedir => ["$ssldir/private",
"Where the client stores private certificate information."],
:passfile => ["$privatedir/password",
"Where puppetd stores the password for its private key. Generally
unused."],
:autosign => ["$confdir/autosign.conf",
"Whether to enable autosign. Valid values are true (which autosigns
any key request, and is a very bad idea), false (which never autosigns
any key request), and the path to a file, which uses that configuration
file to determine which keys to sign."],
:ca_days => [1825, "How long a certificate should be valid."],
:ca_md => ["md5", "The type of hash used in certificates."],
:req_bits => [2048, "The bit length of the certificates."],
:keylength => [1024, "The bit length of keys."]
)
#@@params.each { |param|
# Puppet.setdefault(param,@@defaults[param])
#}
def certfile
@config[:cacert]
end
def host2csrfile(hostname)
File.join(Puppet[:csrdir], [hostname, "pem"].join("."))
end
# this stores signed certs in a directory unrelated to
# normal client certs
def host2certfile(hostname)
File.join(Puppet[:signeddir], [hostname, "pem"].join("."))
end
def thing2name(thing)
thing.subject.to_a.find { |ary|
ary[0] == "CN"
}[1]
end
def initialize(hash = {})
self.setconfig(hash)
if Puppet[:capass]
if FileTest.exists?(Puppet[:capass])
#puts "Reading %s" % Puppet[:capass]
#system "ls -al %s" % Puppet[:capass]
#File.read Puppet[:capass]
@config[:password] = self.getpass
else
# Don't create a password if the cert already exists
unless FileTest.exists?(@config[:cacert])
@config[:password] = self.genpass
end
end
end
self.getcert
unless FileTest.exists?(@config[:serial])
File.open(@config[:serial], "w") { |f|
f << "%04X" % 1
}
end
end
def genpass
pass = ""
20.times { pass += (rand(74) + 48).chr }
Puppet.recmkdir(File.dirname(@config[:capass]))
begin
File.open(@config[:capass], "w", 0600) { |f| f.print pass }
rescue Errno::EACCES => detail
raise Puppet::Error, detail.to_s
end
return pass
end
def getpass
if @config[:capass] and File.readable?(@config[:capass])
return File.read(@config[:capass])
else
raise Puppet::Error, "Could not read CA passfile %s" % @config[:capass]
end
end
def getcert
if FileTest.exists?(@config[:cacert])
@cert = OpenSSL::X509::Certificate.new(
File.read(@config[:cacert])
)
else
self.mkrootcert
end
end
def getclientcsr(host)
csrfile = host2csrfile(host)
unless File.exists?(csrfile)
return nil
end
return OpenSSL::X509::Request.new(File.read(csrfile))
end
def getclientcert(host)
certfile = host2certfile(host)
unless File.exists?(certfile)
return [nil, nil]
end
return [OpenSSL::X509::Certificate.new(File.read(certfile)), @cert]
end
def list
return Dir.entries(Puppet[:csrdir]).reject { |file|
file =~ /^\.+$/
}.collect { |file|
file.sub(/\.pem$/, '')
}
end
def mkrootcert
cert = Certificate.new(
:name => "CAcert",
:cert => @config[:cacert],
:encrypt => @config[:capass],
:key => @config[:cakey],
:selfsign => true,
:length => 1825,
:type => :ca
)
@cert = cert.mkselfsigned
File.open(@config[:cacert], "w", 0660) { |f|
f.puts @cert.to_pem
}
@key = cert.key
return cert
end
def removeclientcsr(host)
csrfile = host2csrfile(host)
unless File.exists?(csrfile)
raise Puppet::Error, "No certificate request for %s" % host
end
File.unlink(csrfile)
end
def setconfig(hash)
@config = {}
Puppet.config.params("ca").each { |param|
param = param.intern if param.is_a? String
if hash.include?(param)
@config[param] = hash[param]
Puppet[param] = hash[param]
hash.delete(param)
else
@config[param] = Puppet[param]
end
}
if hash.include?(:password)
@config[:password] = hash[:password]
hash.delete(:password)
end
if hash.length > 0
raise ArgumentError, "Unknown parameters %s" % hash.keys.join(",")
end
[:cadir, :csrdir, :signeddir].each { |dir|
unless @config[dir]
raise Puppet::DevError, "%s is undefined" % dir
end
unless FileTest.exists?(@config[dir])
Puppet.recmkdir(@config[dir])
end
}
end
def sign(csr)
unless csr.is_a?(OpenSSL::X509::Request)
raise Puppet::Error,
"CA#sign only accepts OpenSSL::X509::Request objects, not %s" %
csr.class
end
unless csr.verify(csr.public_key)
raise Puppet::Error, "CSR sign verification failed"
end
# i should probably check key length...
# read the ca cert in
cacert = OpenSSL::X509::Certificate.new(
File.read(@config[:cacert])
)
cakey = nil
if @config[:password]
cakey = OpenSSL::PKey::RSA.new(
File.read(@config[:cakey]), @config[:password]
)
else
system("ls -al %s" % Puppet[:capass])
cakey = OpenSSL::PKey::RSA.new(
File.read(@config[:cakey])
)
end
unless cacert.check_private_key(cakey)
raise Puppet::Error, "CA Certificate is invalid"
end
serial = File.read(@config[:serial]).chomp.hex
newcert = Puppet::SSLCertificates.mkcert(
:type => :server,
:name => csr.subject,
:days => @config[:ca_days],
:issuer => cacert,
:serial => serial,
:publickey => csr.public_key
)
# increment the serial
File.open(@config[:serial], "w") { |f|
f << "%04X" % (serial + 1)
}
newcert.sign(cakey, OpenSSL::Digest::SHA1.new)
self.storeclientcert(newcert)
return [newcert, cacert]
end
def storeclientcsr(csr)
host = thing2name(csr)
csrfile = host2csrfile(host)
if File.exists?(csrfile)
raise Puppet::Error, "Certificate request for %s already exists" % host
end
File.open(csrfile, "w", 0660) { |f|
f.print csr.to_pem
}
end
def storeclientcert(cert)
host = thing2name(cert)
certfile = host2certfile(host)
if File.exists?(certfile)
Puppet.notice "Overwriting signed certificate %s for %s" %
[certfile, host]
end
File.open(certfile, "w", 0660) { |f|
f.print cert.to_pem
}
end
end
# $Id$
|