-
Notifications
You must be signed in to change notification settings - Fork 0
/
leaf.go
266 lines (225 loc) · 7.13 KB
/
leaf.go
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
// Copyright 2020 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.
package pki
import (
"bytes"
"crypto"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"net"
"sort"
"github.com/juju/collections/set"
"github.com/juju/errors"
)
// DefaultLeaf is a default implementation of the Leaf interface
type DefaultLeaf struct {
group string
certificate *x509.Certificate
chain []*x509.Certificate
signer crypto.Signer
tlsCertificate *tls.Certificate
}
// DefaultLeafRequest is a default implementation of the LeafRequest interface
type DefaultLeafRequest struct {
dnsNames set.Strings
ipAddresses map[string]net.IP
leafMaker LeafMaker
requestSigner CertificateRequestSigner
signer crypto.Signer
subject pkix.Name
}
// Leaf represents a certificate and is associated key for signing operations.
type Leaf interface {
// Certificate returns the x509 certificate of this leaf. May be nil if no
// certificate exists yet. Call Commit to sign the leaf.
Certificate() *x509.Certificate
// Chain is the certificate signing chain for this leaf in the case of
// intermediate CA's
Chain() []*x509.Certificate
// Signer is the crypto key used for signing operations on this leaf.
Signer() crypto.Signer
// Convenience method for generating a tls certificate for use in tls
// transport.
TLSCertificate() *tls.Certificate
// Convenience method for converting this leaf to pem parts of
// certificate/chain and private key
ToPemParts() (cert, key []byte, err error)
}
// LeafMaker describes a function that can construct new Leaf's from the
// supplied certificate and crypto signer
type LeafMaker func(*x509.Certificate, []*x509.Certificate, crypto.Signer) (Leaf, error)
// LeafRequest is an intermediate unit for requesting new leafs with specific
// attributes.
type LeafRequest interface {
// AddDNSNames adds the specificed dns names to the LeafRequest
AddDNSNames(...string) LeafRequest
// AddIPAddresses adds the specificed ip addresses to the LeafRequest
AddIPAddresses(...net.IP) LeafRequest
// Commit transforms the LeafRequest to a new Leaf
Commit() (Leaf, error)
}
var (
HeaderLeafGroup = "leaf.pki.juju.is/group"
)
// AddDNSNames implements LeafRequest AddDNSNames
func (d *DefaultLeafRequest) AddDNSNames(dnsNames ...string) LeafRequest {
d.dnsNames = d.dnsNames.Union(set.NewStrings(dnsNames...))
return d
}
// AddIPAddresses implements LeafRequest AddIPAddresses
func (d *DefaultLeafRequest) AddIPAddresses(ipAddresses ...net.IP) LeafRequest {
for _, ipAddress := range ipAddresses {
ipStr := ipAddress.String()
if _, exists := d.ipAddresses[ipStr]; !exists {
d.ipAddresses[ipStr] = ipAddress
}
}
return d
}
// Certificate implements Leaf Certificate
func (d *DefaultLeaf) Certificate() *x509.Certificate {
return d.certificate
}
// Chain implements Leaf Chain
func (d *DefaultLeaf) Chain() []*x509.Certificate {
return d.chain
}
// Commit implements Leaf Commit
func (d *DefaultLeafRequest) Commit() (Leaf, error) {
var err error
if d.signer == nil {
d.signer, err = DefaultKeyProfile()
if err != nil {
return nil, errors.Trace(err)
}
}
csr := &x509.CertificateRequest{
DNSNames: d.dnsNames.Values(),
IPAddresses: ipAddressMapToSlice(d.ipAddresses),
PublicKey: d.signer.Public(),
Subject: d.subject,
}
cert, chain, err := d.requestSigner.SignCSR(csr)
if err != nil {
return nil, errors.Annotate(err, "signing CSR for leaf")
}
return d.leafMaker(cert, chain, d.signer)
}
// LeafHasDNSNames tests a diven Leaf to see if it contains the supplied DNS
// names
func LeafHasDNSNames(leaf Leaf, dnsNames []string) bool {
certDNSNames := leaf.Certificate().DNSNames
if len(certDNSNames) < len(dnsNames) {
return false
}
a := make([]string, len(certDNSNames))
copy(a, certDNSNames)
sort.Strings(a)
sort.Strings(dnsNames)
for _, name := range dnsNames {
index := sort.SearchStrings(a, name)
if index == len(a) || a[index] != name {
return false
}
}
return true
}
func ipAddressMapToSlice(m map[string]net.IP) []net.IP {
rval := make([]net.IP, len(m))
i := 0
for _, v := range m {
rval[i] = v
i = i + 1
}
return rval
}
// NewDefaultLeaf constructs a new DefaultLeaf for the supplied certificate and
// key
func NewDefaultLeaf(group string, cert *x509.Certificate,
chain []*x509.Certificate, signer crypto.Signer) *DefaultLeaf {
tlsCert := &tls.Certificate{
Certificate: make([][]byte, len(chain)+1),
PrivateKey: signer,
Leaf: cert,
}
tlsCert.Certificate[0] = cert.Raw
for i, chainCert := range chain {
tlsCert.Certificate[i+1] = chainCert.Raw
}
return &DefaultLeaf{
group: group,
certificate: cert,
chain: chain,
signer: signer,
tlsCertificate: tlsCert,
}
}
// NewDefaultLeafPem constructs a new DefaultLeaf from the supplied PEM data
func NewDefaultLeafPem(group string, pemBlock []byte) (*DefaultLeaf, error) {
certs, signers, err := UnmarshalPemData(pemBlock)
if err != nil {
return nil, errors.Trace(err)
}
if len(certs) == 0 {
return nil, errors.New("found zero certificates in pem bundle")
}
if len(signers) != 1 {
return nil, errors.New("expected at least one private key in bundle")
}
if !PublicKeysEqual(signers[0].Public(), certs[0].PublicKey) {
return nil, errors.New("public keys of first certificate and key do not match")
}
return NewDefaultLeaf(group, certs[0], certs[1:], signers[0]), nil
}
// NewDefaultLeafRequest create a DefaultLeafRequest object that implements
// LeafRequest
func NewDefaultLeafRequest(subject pkix.Name,
requestSigner CertificateRequestSigner, maker LeafMaker) *DefaultLeafRequest {
return &DefaultLeafRequest{
dnsNames: set.Strings{},
ipAddresses: map[string]net.IP{},
leafMaker: maker,
requestSigner: requestSigner,
subject: subject,
}
}
// NewDefaultLeafRequestWithSigner create a DefaultLeafRequest object that
// implements LeafRequest. Takes a default signer to use for all certificate
// creation instead of generating a new one.
func NewDefaultLeafRequestWithSigner(subject pkix.Name, signer crypto.Signer,
requestSigner CertificateRequestSigner,
maker LeafMaker) *DefaultLeafRequest {
return &DefaultLeafRequest{
dnsNames: set.Strings{},
ipAddresses: map[string]net.IP{},
leafMaker: maker,
requestSigner: requestSigner,
signer: signer,
subject: subject,
}
}
// Signer implements Leaf interface Signer
func (d *DefaultLeaf) Signer() crypto.Signer {
return d.signer
}
// TLSCertificate implements Leaf interface TLSCertificate
func (d *DefaultLeaf) TLSCertificate() *tls.Certificate {
return d.tlsCertificate
}
// ToPemParts implements Leaf interface ToPemParts
func (d *DefaultLeaf) ToPemParts() ([]byte, []byte, error) {
certBuf := bytes.Buffer{}
err := CertificateToPemWriter(&certBuf, map[string]string{
HeaderLeafGroup: d.group,
}, d.Certificate(), d.Chain()...)
if err != nil {
return nil, nil, errors.Annotate(err, "turning leaf certificate to pem")
}
keyBuf := bytes.Buffer{}
err = SignerToPemWriter(&keyBuf, d.Signer())
if err != nil {
return nil, nil, errors.Annotate(err, "turning leaf key to pem")
}
return certBuf.Bytes(), keyBuf.Bytes(), nil
}