~ubuntu-branches/ubuntu/oneiric/puppet/oneiric-security

« back to all changes in this revision

Viewing changes to lib/puppet/sslcertificates/ca.rb

  • Committer: Bazaar Package Importer
  • Author(s): Micah Anderson
  • Date: 2008-07-26 15:43:45 UTC
  • mto: (3.1.1 lenny) (1.3.1 upstream)
  • mto: This revision was merged to the branch mainline in revision 16.
  • Revision ID: james.westby@ubuntu.com-20080726154345-1fmgo76b4l72ulvc
ImportĀ upstreamĀ versionĀ 0.24.5

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
require 'sync'
 
2
 
1
3
class Puppet::SSLCertificates::CA
2
4
    include Puppet::Util::Warnings
3
5
 
4
6
    Certificate = Puppet::SSLCertificates::Certificate
5
7
    attr_accessor :keyfile, :file, :config, :dir, :cert, :crl
6
8
 
7
 
    Puppet.setdefaults(:ca,
8
 
        :cadir => {  :default => "$ssldir/ca",
9
 
            :owner => "$user",
10
 
            :group => "$group",
11
 
            :mode => 0770,
12
 
            :desc => "The root directory for the certificate authority."
13
 
        },
14
 
        :cacert => { :default => "$cadir/ca_crt.pem",
15
 
            :owner => "$user",
16
 
            :group => "$group",
17
 
            :mode => 0660,
18
 
            :desc => "The CA certificate."
19
 
        },
20
 
        :cakey => { :default => "$cadir/ca_key.pem",
21
 
            :owner => "$user",
22
 
            :group => "$group",
23
 
            :mode => 0660,
24
 
            :desc => "The CA private key."
25
 
        },
26
 
        :capub => { :default => "$cadir/ca_pub.pem",
27
 
            :owner => "$user",
28
 
            :group => "$group",
29
 
            :desc => "The CA public key."
30
 
        },
31
 
        :cacrl => { :default => "$cadir/ca_crl.pem",
32
 
            :owner => "$user",
33
 
            :group => "$group",
34
 
            :mode => 0664,
35
 
            :desc => "The certificate revocation list (CRL) for the CA. Set this to 'none' if you do not want to use a CRL."
36
 
        },
37
 
        :caprivatedir => { :default => "$cadir/private",
38
 
            :owner => "$user",
39
 
            :group => "$group",
40
 
            :mode => 0770,
41
 
            :desc => "Where the CA stores private certificate information."
42
 
        },
43
 
        :csrdir => { :default => "$cadir/requests",
44
 
            :owner => "$user",
45
 
            :group => "$group",
46
 
            :desc => "Where the CA stores certificate requests"
47
 
        },
48
 
        :signeddir => { :default => "$cadir/signed",
49
 
            :owner => "$user",
50
 
            :group => "$group",
51
 
            :mode => 0770,
52
 
            :desc => "Where the CA stores signed certificates."
53
 
        },
54
 
        :capass => { :default => "$caprivatedir/ca.pass",
55
 
            :owner => "$user",
56
 
            :group => "$group",
57
 
            :mode => 0660,
58
 
            :desc => "Where the CA stores the password for the private key"
59
 
        },
60
 
        :serial => { :default => "$cadir/serial",
61
 
            :owner => "$user",
62
 
            :group => "$group",
63
 
            :desc => "Where the serial number for certificates is stored."
64
 
        },
65
 
        :autosign => { :default => "$confdir/autosign.conf",
66
 
            :mode => 0644,
67
 
            :desc => "Whether to enable autosign.  Valid values are true (which
68
 
                autosigns any key request, and is a very bad idea), false (which
69
 
                never autosigns any key request), and the path to a file, which
70
 
                uses that configuration file to determine which keys to sign."},
71
 
        :ca_days => ["", "How long a certificate should be valid. 
72
 
                 This parameter is deprecated, use ca_ttl instead"],
73
 
        :ca_ttl => ["5y", "The default TTL for new certificates; valid values 
74
 
                must be an integer, optionally followed by one of the units 
75
 
                'y' (years of 365 days), 'd' (days), 'h' (hours), or 
76
 
                's' (seconds). The unit defaults to seconds. If this parameter
77
 
                is set, ca_days is ignored. Examples are '3600' (one hour) 
78
 
                and '1825d', which is the same as '5y' (5 years) "],
79
 
        :ca_md => ["md5", "The type of hash used in certificates."],
80
 
        :req_bits => [2048, "The bit length of the certificates."],
81
 
        :keylength => [1024, "The bit length of keys."]
82
 
    )
83
 
 
84
9
    def certfile
85
10
        @config[:cacert]
86
11
    end
87
12
 
88
 
    # TTL for new certificates in seconds. If config param :ca_ttl is set, 
89
 
    # use that, otherwise use :ca_days for backwards compatibility
90
 
    def ttl
91
 
        days = @config[:ca_days]
92
 
        if days && days.size > 0
93
 
            warnonce "Parameter ca_ttl is not set. Using depecated ca_days instead."
94
 
            return @config[:ca_days] * 24 * 60 * 60
95
 
        else
96
 
            ttl = @config[:ca_ttl]
97
 
            if ttl.is_a?(String)
98
 
                unless ttl =~ /^(\d+)(y|d|h|s)$/
99
 
                    raise ArgumentError, "Invalid ca_ttl #{ttl}"
100
 
                end
101
 
                case $2
102
 
                when 'y'
103
 
                    unit = 365 * 24 * 60 * 60
104
 
                when 'd'
105
 
                    unit = 24 * 60 * 60
106
 
                when 'h'
107
 
                    unit = 60 * 60
108
 
                when 's'
109
 
                    unit = 1
110
 
                else
111
 
                    raise ArgumentError, "Invalid unit for ca_ttl #{ttl}"
112
 
                end
113
 
                return $1.to_i * unit
114
 
            else
115
 
                return ttl
116
 
            end
117
 
        end
118
 
    end
119
 
 
120
13
    # Remove all traces of a given host.  This is kind of hackish, but, eh.
121
14
    def clean(host)
 
15
        host = host.downcase
122
16
        [:csrdir, :signeddir, :publickeydir, :privatekeydir, :certdir].each do |name|
123
17
            dir = Puppet[name]
124
18
 
126
20
 
127
21
            if FileTest.exists?(file)
128
22
                begin
129
 
                    if Puppet.name == "puppetca"
 
23
                    if Puppet[:name] == "puppetca"
130
24
                        puts "Removing %s" % file
131
25
                    else
132
26
                        Puppet.info "Removing %s" % file
142
36
    end
143
37
 
144
38
    def host2csrfile(hostname)
145
 
        File.join(Puppet[:csrdir], [hostname, "pem"].join("."))
 
39
        File.join(Puppet[:csrdir], [hostname.downcase, "pem"].join("."))
146
40
    end
147
41
 
148
42
    # this stores signed certs in a directory unrelated to 
149
43
    # normal client certs
150
44
    def host2certfile(hostname)
151
 
        File.join(Puppet[:signeddir], [hostname, "pem"].join("."))
 
45
        File.join(Puppet[:signeddir], [hostname.downcase, "pem"].join("."))
152
46
    end
153
47
 
154
48
    # Turn our hostname into a Name object
159
53
    end
160
54
 
161
55
    def initialize(hash = {})
162
 
        Puppet.config.use(:puppet, :certificates, :ca)
 
56
        Puppet.settings.use(:main, :ca, :ssl)
163
57
        self.setconfig(hash)
164
58
 
165
59
        if Puppet[:capass]
179
73
        self.getcert
180
74
        init_crl
181
75
        unless FileTest.exists?(@config[:serial])
182
 
            Puppet.config.write(:serial) do |f|
 
76
            Puppet.settings.write(:serial) do |f|
183
77
                f << "%04X" % 1
184
78
            end
185
79
        end
191
85
        20.times { pass += (rand(74) + 48).chr }
192
86
 
193
87
        begin
194
 
            Puppet.config.write(:capass) { |f| f.print pass }
 
88
            Puppet.settings.write(:capass) { |f| f.print pass }
195
89
        rescue Errno::EACCES => detail
196
90
            raise Puppet::Error, detail.to_s
197
91
        end
203
97
        if @config[:capass] and File.readable?(@config[:capass])
204
98
            return File.read(@config[:capass])
205
99
        else
206
 
            raise Puppet::Error, "Could not read CA passfile %s" % @config[:capass]
 
100
            raise Puppet::Error, "Could not decrypt CA key with password: %s" % detail
207
101
        end
208
102
    end
209
103
 
238
132
        return [OpenSSL::X509::Certificate.new(File.read(certfile)), @cert]
239
133
    end
240
134
 
241
 
    # List certificates waiting to be signed.
 
135
    # List certificates waiting to be signed.  This returns a list of hostnames, not actual
 
136
    # files -- the names can be converted to full paths with host2csrfile.
242
137
    def list
243
138
        return Dir.entries(Puppet[:csrdir]).find_all { |file|
244
139
            file =~ /\.pem$/
247
142
        }
248
143
    end
249
144
 
 
145
    # List signed certificates.  This returns a list of hostnames, not actual 
 
146
    # files -- the names can be converted to full paths with host2csrfile. 
 
147
    def list_signed 
 
148
        return Dir.entries(Puppet[:signeddir]).find_all { |file| 
 
149
            file =~ /\.pem$/ 
 
150
        }.collect { |file| 
 
151
            file.sub(/\.pem$/, '') 
 
152
        } 
 
153
    end 
 
154
 
250
155
    # Create the root certificate.
251
156
    def mkrootcert
252
157
        # Make the root cert's name the FQDN of the host running the CA.
265
170
        )
266
171
 
267
172
        # This creates the cakey file
268
 
        Puppet::SUIDManager.asuser(Puppet[:user], Puppet[:group]) do
 
173
        Puppet::Util::SUIDManager.asuser(Puppet[:user], Puppet[:group]) do
269
174
            @cert = cert.mkselfsigned
270
175
        end
271
 
        Puppet.config.write(:cacert) do |f|
 
176
        Puppet.settings.write(:cacert) do |f|
272
177
            f.puts @cert.to_pem
273
178
        end
 
179
        Puppet.settings.write(:capub) do |f|
 
180
            f.puts @cert.public_key
 
181
        end
274
182
        return cert
275
183
    end
276
184
 
283
191
        File.unlink(csrfile)
284
192
    end
285
193
 
 
194
    # Revoke the certificate with serial number SERIAL issued by this
 
195
    # CA. The REASON must be one of the OpenSSL::OCSP::REVOKED_* reasons
 
196
    def revoke(serial, reason = OpenSSL::OCSP::REVOKED_STATUS_KEYCOMPROMISE)
 
197
        if @config[:cacrl] == 'false'
 
198
            raise Puppet::Error, "Revocation requires a CRL, but ca_crl is set to 'false'"
 
199
        end
 
200
        time = Time.now
 
201
        revoked = OpenSSL::X509::Revoked.new
 
202
        revoked.serial = serial
 
203
        revoked.time = time
 
204
        enum = OpenSSL::ASN1::Enumerated(reason)
 
205
        ext = OpenSSL::X509::Extension.new("CRLReason", enum)
 
206
        revoked.add_extension(ext)
 
207
        @crl.add_revoked(revoked)
 
208
        store_crl
 
209
    end
 
210
    
286
211
    # Take the Puppet config and store it locally.
287
212
    def setconfig(hash)
288
213
        @config = {}
289
 
        Puppet.config.params("ca").each { |param|
 
214
        Puppet.settings.params("ca").each { |param|
290
215
            param = param.intern if param.is_a? String
291
216
            if hash.include?(param)
292
217
                @config[param] = hash[param]
325
250
            raise Puppet::Error, "CSR sign verification failed"
326
251
        end
327
252
 
328
 
        serial = File.read(@config[:serial]).chomp.hex
 
253
        serial = nil
 
254
        Puppet.settings.readwritelock(:serial) { |f|
 
255
            serial = File.read(@config[:serial]).chomp.hex
 
256
            # increment the serial
 
257
            f << "%04X" % (serial + 1)
 
258
        }
 
259
 
329
260
        newcert = Puppet::SSLCertificates.mkcert(
330
261
            :type => :server,
331
262
            :name => csr.subject,
335
266
            :publickey => csr.public_key
336
267
        )
337
268
 
338
 
        # increment the serial
339
 
        Puppet.config.write(:serial) do |f|
340
 
            f << "%04X" % (serial + 1)
341
 
        end
342
269
 
343
270
        sign_with_key(newcert)
344
271
 
358
285
            raise Puppet::Error, "Certificate request for %s already exists" % host
359
286
        end
360
287
 
361
 
        Puppet.config.writesub(:csrdir, csrfile) do |f|
 
288
        Puppet.settings.writesub(:csrdir, csrfile) do |f|
362
289
            f.print csr.to_pem
363
290
        end
364
291
    end
365
292
 
366
 
    # Revoke the certificate with serial number SERIAL issued by this
367
 
    # CA. The REASON must be one of the OpenSSL::OCSP::REVOKED_* reasons
368
 
    def revoke(serial, reason = OpenSSL::OCSP::REVOKED_STATUS_KEYCOMPROMISE)
369
 
        if @config[:cacrl] == 'none'
370
 
            raise Puppet::Error, "Revocation requires a CRL, but ca_crl is set to 'none'"
371
 
        end
372
 
        time = Time.now
373
 
        revoked = OpenSSL::X509::Revoked.new
374
 
        revoked.serial = serial
375
 
        revoked.time = time
376
 
        enum = OpenSSL::ASN1::Enumerated(reason)
377
 
        ext = OpenSSL::X509::Extension.new("CRLReason", enum)
378
 
        revoked.add_extension(ext)
379
 
        @crl.add_revoked(revoked)
380
 
        store_crl
381
 
    end
382
 
 
383
293
    # Store the certificate that we generate.
384
294
    def storeclientcert(cert)
385
295
        host = thing2name(cert)
391
301
        end
392
302
 
393
303
        Puppet::SSLCertificates::Inventory::add(cert)
394
 
        Puppet.config.writesub(:signeddir, certfile) do |f|
 
304
        Puppet.settings.writesub(:signeddir, certfile) do |f|
395
305
            f.print cert.to_pem
396
306
        end
397
307
    end
398
308
 
 
309
    # TTL for new certificates in seconds. If config param :ca_ttl is set, 
 
310
    # use that, otherwise use :ca_days for backwards compatibility
 
311
    def ttl
 
312
        days = @config[:ca_days]
 
313
        if days && days.size > 0
 
314
            warnonce "Parameter ca_ttl is not set. Using depecated ca_days instead."
 
315
            return @config[:ca_days] * 24 * 60 * 60
 
316
        else
 
317
            ttl = @config[:ca_ttl]
 
318
            if ttl.is_a?(String)
 
319
                unless ttl =~ /^(\d+)(y|d|h|s)$/
 
320
                    raise ArgumentError, "Invalid ca_ttl #{ttl}"
 
321
                end
 
322
                case $2
 
323
                when 'y'
 
324
                    unit = 365 * 24 * 60 * 60
 
325
                when 'd'
 
326
                    unit = 24 * 60 * 60
 
327
                when 'h'
 
328
                    unit = 60 * 60
 
329
                when 's'
 
330
                    unit = 1
 
331
                else
 
332
                    raise ArgumentError, "Invalid unit for ca_ttl #{ttl}"
 
333
                end
 
334
                return $1.to_i * unit
 
335
            else
 
336
                return ttl
 
337
            end
 
338
        end
 
339
    end
 
340
    
399
341
    private
400
342
    def init_crl
401
343
        if FileTest.exists?(@config[:cacrl])
402
344
            @crl = OpenSSL::X509::CRL.new(
403
345
                File.read(@config[:cacrl])
404
346
            )
405
 
        elsif @config[:cacrl] == 'none'
 
347
        elsif @config[:cacrl] == 'false'
406
348
            @crl = nil
407
349
        else
408
350
            # Create new CRL
429
371
        @crl.next_update = now + 5 * 365*24*60*60
430
372
 
431
373
        sign_with_key(@crl)
432
 
        Puppet.config.write(:cacrl) do |f|
 
374
        Puppet.settings.write(:cacrl) do |f|
433
375
            f.puts @crl.to_pem
434
376
        end
435
377
    end
437
379
    def sign_with_key(signable, digest = OpenSSL::Digest::SHA1.new)
438
380
        cakey = nil
439
381
        if @config[:password]
440
 
            cakey = OpenSSL::PKey::RSA.new(
441
 
                File.read(@config[:cakey]), @config[:password]
442
 
            )
 
382
            begin
 
383
                cakey = OpenSSL::PKey::RSA.new(
 
384
                    File.read(@config[:cakey]), @config[:password]
 
385
                )
 
386
            rescue
 
387
                raise Puppet::Error,
 
388
                    "Decrypt of CA private key with password stored in @config[:capass] not possible"
 
389
            end
443
390
        else
444
391
            cakey = OpenSSL::PKey::RSA.new(
445
392
                File.read(@config[:cakey])
454
401
    end
455
402
end
456
403
 
457
 
# $Id: ca.rb 1666 2006-09-22 17:19:02Z erikh $