summaryrefslogtreecommitdiffstats
path: root/lib/puppet/sslcertificates.rb
blob: 8c93139a98fe3606db15410e9fac01770f8b73ff (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
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
# The library for manipulating SSL certs.

require 'puppet'

begin
    require 'openssl'
rescue LoadError
    raise Puppet::Error, "You must have the Ruby openssl library installed"
end

module Puppet::SSLCertificates
    hostname = Facter["hostname"].value
    domain = Facter["domain"].value
    fqdn = [hostname, domain].join(".")

    Puppet.setdefaults("certificates",
        :certdir => ["$ssldir/certs", "The certificate directory."],
        :publickeydir => ["$ssldir/public_keys", "The public key directory."],
        :privatekeydir => { :default => "$ssldir/private_keys",
            :mode => 0750,
            :desc => "The private key directory."
        },
        :privatedir => { :default => "$ssldir/private",
            :mode => 0750,
            :desc => "Where the client stores private certificate information."
        },
        :passfile => { :default => "$privatedir/password",
            :mode => 0640,
            :desc => "Where puppetd stores the password for its private key.
                Generally unused."
        },
        :hostcert => { :default => "$certdir/#{fqdn}.pem",
            :mode => 0644,
            :desc => "Where individual hosts store and look for their certificates."
        },
        :hostprivkey => { :default => "$privatekeydir/#{fqdn}.pem",
            :mode => 0600,
            :desc => "Where individual hosts store and look for their private key."
        },
        :hostpubkey => { :default => "$publickeydir/#{fqdn}.pem",
            :mode => 0644,
            :desc => "Where individual hosts store and look for their public key."
        },
        :localcacert => { :default => "$certdir/ca.pem",
            :mode => 0644,
            :desc => "Where each client stores the CA certificate."
        }
    )

    #def self.mkcert(type, name, ttl, issuercert, issuername, serial, publickey)
    def self.mkcert(hash)
        [:type, :name, :ttl, :issuer, :serial, :publickey].each { |param|
            unless hash.include?(param)
                raise ArgumentError, "mkcert called without %s" % param
            end
        }

        cert = OpenSSL::X509::Certificate.new
        from = Time.now

        cert.subject = hash[:name]
        if hash[:issuer]
            cert.issuer = hash[:issuer].subject
        else
            # we're a self-signed cert
            cert.issuer = hash[:name]
        end
        cert.not_before = from
        cert.not_after = from + hash[:ttl]
        cert.version = 2 # X509v3

        cert.public_key = hash[:publickey]
        cert.serial = hash[:serial]

        basic_constraint = nil
        key_usage = nil
        ext_key_usage = nil

        ef = OpenSSL::X509::ExtensionFactory.new

        ef.subject_certificate = cert

        if hash[:issuer]
            ef.issuer_certificate = hash[:issuer]
        else
            ef.issuer_certificate = cert
        end

        ex = []
        case hash[:type]
        when :ca:
            basic_constraint = "CA:TRUE"
            key_usage = %w{cRLSign keyCertSign}
        when :terminalsubca:
            basic_constraint = "CA:TRUE,pathlen:0"
            key_usage = %w{cRLSign keyCertSign}
        when :server:
            basic_constraint = "CA:FALSE"
            key_usage = %w{digitalSignature keyEncipherment}
        ext_key_usage = %w{serverAuth clientAuth}
        when :ocsp:
            basic_constraint = "CA:FALSE"
            key_usage = %w{nonRepudiation digitalSignature}
        ext_key_usage = %w{serverAuth OCSPSigning}
        when :client:
            basic_constraint = "CA:FALSE"
            key_usage = %w{nonRepudiation digitalSignature keyEncipherment}
        ext_key_usage = %w{clientAuth emailProtection}
            ex << ef.create_extension("nsCertType", "client,email")
        else
            raise Puppet::Error, "unknown cert type '%s'" % hash[:type]
        end

        ex << ef.create_extension("nsComment",
                                  "Puppet Ruby/OpenSSL Generated Certificate")
        ex << ef.create_extension("basicConstraints", basic_constraint, true)
        ex << ef.create_extension("subjectKeyIdentifier", "hash")

        if key_usage
          ex << ef.create_extension("keyUsage", key_usage.join(","))
        end
        if ext_key_usage
          ex << ef.create_extension("extendedKeyUsage", ext_key_usage.join(","))
        end

        #if @ca_config[:cdp_location] then
        #  ex << ef.create_extension("crlDistributionPoints",
        #                            @ca_config[:cdp_location])
        #end

        #if @ca_config[:ocsp_location] then
        #  ex << ef.create_extension("authorityInfoAccess",
        #                            "OCSP;" << @ca_config[:ocsp_location])
        #end
        cert.extensions = ex

        # for some reason this _must_ be the last extension added
        if hash[:type] == :ca
            ex << ef.create_extension("authorityKeyIdentifier",
                                      "keyid:always,issuer:always")
        end

        return cert
    end

    def self.mkhash(dir, cert, certfile)
        # Make sure the hash is zero-padded to 8 chars
        hash = "%08x" % cert.issuer.hash
        hashpath = nil
        10.times { |i|
            path = File.join(dir, "%s.%s" % [hash, i])
            if FileTest.exists?(path)
                if FileTest.symlink?(path)
                    dest = File.readlink(path)
                    if dest == certfile
                        # the correct link already exists
                        hashpath = path
                        break
                    else
                        next
                    end
                else
                    next
                end
            end

            File.symlink(certfile, path)

            hashpath = path
            break
        }


        return hashpath
    end
    require 'puppet/sslcertificates/certificate'
    require 'puppet/sslcertificates/inventory'
    require 'puppet/sslcertificates/ca'
end

# $Id$