summaryrefslogtreecommitdiffstats
path: root/lib/puppet/ssl/certificate_authority.rb
blob: aa997aaf6c74798aae434eba8e2871d03e25019f (plain)
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
require 'puppet/ssl/host'

# The class that knows how to sign certificates.  It's just a
# special case of the SSL::Host -- it's got a sign method,
# and it reads its info from a different location.
class Puppet::SSL::CertificateAuthority < Puppet::SSL::Host
    require 'puppet/ssl/certificate_factory'

    # Provide the path to our password, and read our special ca key.
    def read_key
        return nil unless FileTest.exist?(Puppet[:cakey])

        key = Puppet::SSL::Key.new(name)
        key.password_file = Puppet[:capass]
        key.read(Puppet[:cakey])

        return key
    end

    # Generate and write the key out.
    def generate_key
        @key = Key.new(name)
        @key.password_file = Puppet[:capass]
        @key.generate
        Puppet.settings.write(:cakey) do |f|
            f.print @key.to_s
        end
        true
    end

    # Read the special path to our key.
    def read_certificate
        return nil unless FileTest.exist?(Puppet[:cacert])
        cert = Puppet::SSL::Certificate.new(name)
        cert.read(Puppet[:cacert])

        return cert
    end

    # The CA creates a self-signed certificate, rather than relying
    # on someone else to do the work.
    def generate_certificate
        request = CertificateRequest.new(name)
        request.generate(key)

        # Create a self-signed certificate.
        @certificate = sign(name, :ca, request)

        Puppet.settings.write(:cacert) do |f|
            f.print @certificate.to_s
        end

        return true
    end

    def initialize
        Puppet.settings.use :main, :ssl, :ca

        # Always name the ca after the host we're running on.
        super(Puppet[:certname])

        setup_ca()
    end

    # Sign a given certificate request.
    def sign(host, cert_type = :server, self_signing_csr = nil)

        # This is a self-signed certificate
        if self_signing_csr
            csr = self_signing_csr
            issuer = csr.content
        else
            raise ArgumentError, "Cannot find CA certificate; cannot sign certificate for %s" % host unless certificate
            unless csr = Puppet::SSL::CertificateRequest.find(host, :in => :ca_file)
                raise ArgumentError, "Could not find certificate request for %s" % host
            end
            issuer = certificate
        end

        cert = Puppet::SSL::Certificate.new(host)
        cert.content = Puppet::SSL::CertificateFactory.new(cert_type, csr.content, issuer, next_serial).result

        Puppet.notice "Signed certificate request for %s" % host

        # Save the now-signed cert, unless it's a self-signed cert, since we
        # assume it goes somewhere else.
        cert.save(:in => :ca_file) unless self_signing_csr

        return cert
    end

    # Do all of the initialization necessary to set up our
    # ca.
    def setup_ca
        # Make sure we've got a password protecting our private key.
        generate_password unless password?

        generate_key unless key

        # And then make sure we've got the whole kaboodle.  This will
        # create a self-signed CA certificate if we don't already have one,
        # and it will just read it in if we do.
        generate_certificate unless certificate
    end

    # Generate a new password for the CA.
    def generate_password
        pass = ""
        20.times { pass += (rand(74) + 48).chr }

        begin
            Puppet.settings.write(:capass) { |f| f.print pass }
        rescue Errno::EACCES => detail
            raise Puppet::Error, "Could not write CA password: %s" % detail.to_s
        end

        @password = pass

        return pass
    end

    # Read the next serial from the serial file, and increment the
    # file so this one is considered used.
    def next_serial
        serial = nil
        Puppet.settings.readwritelock(:serial) { |f|
            if FileTest.exist?(Puppet[:serial])
                serial = File.read(Puppet.settings[:serial]).chomp.hex
            else
                serial = 0x0
            end

            # We store the next valid serial, not the one we just used.
            f << "%04X" % (serial + 1)
        }

        return serial
    end

    # Does the password file exist?
    def password?
        FileTest.exist? Puppet[:capass]
    end
end