github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/pki/leaf.go (about) 1 // Copyright 2020 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package pki 5 6 import ( 7 "bytes" 8 "crypto" 9 "crypto/tls" 10 "crypto/x509" 11 "crypto/x509/pkix" 12 "net" 13 "sort" 14 15 "github.com/juju/collections/set" 16 "github.com/juju/errors" 17 ) 18 19 // DefaultLeaf is a default implementation of the Leaf interface 20 type DefaultLeaf struct { 21 group string 22 certificate *x509.Certificate 23 chain []*x509.Certificate 24 signer crypto.Signer 25 tlsCertificate *tls.Certificate 26 } 27 28 // DefaultLeafRequest is a default implementation of the LeafRequest interface 29 type DefaultLeafRequest struct { 30 dnsNames set.Strings 31 ipAddresses map[string]net.IP 32 leafMaker LeafMaker 33 requestSigner CertificateRequestSigner 34 signer crypto.Signer 35 subject pkix.Name 36 } 37 38 // Leaf represents a certificate and is associated key for signing operations. 39 type Leaf interface { 40 // Certificate returns the x509 certificate of this leaf. May be nil if no 41 // certificate exists yet. Call Commit to sign the leaf. 42 Certificate() *x509.Certificate 43 44 // Chain is the certificate signing chain for this leaf in the case of 45 // intermediate CA's 46 Chain() []*x509.Certificate 47 48 // Signer is the crypto key used for signing operations on this leaf. 49 Signer() crypto.Signer 50 51 // Convenience method for generating a tls certificate for use in tls 52 // transport. 53 TLSCertificate() *tls.Certificate 54 55 // Convenience method for converting this leaf to pem parts of 56 // certificate/chain and private key 57 ToPemParts() (cert, key []byte, err error) 58 } 59 60 // LeafMaker describes a function that can construct new Leaf's from the 61 // supplied certificate and crypto signer 62 type LeafMaker func(*x509.Certificate, []*x509.Certificate, crypto.Signer) (Leaf, error) 63 64 // LeafRequest is an intermediate unit for requesting new leafs with specific 65 // attributes. 66 type LeafRequest interface { 67 // AddDNSNames adds the specificed dns names to the LeafRequest 68 AddDNSNames(...string) LeafRequest 69 70 // AddIPAddresses adds the specificed ip addresses to the LeafRequest 71 AddIPAddresses(...net.IP) LeafRequest 72 73 // Commit transforms the LeafRequest to a new Leaf 74 Commit() (Leaf, error) 75 } 76 77 var ( 78 HeaderLeafGroup = "leaf.pki.juju.is/group" 79 ) 80 81 // AddDNSNames implements LeafRequest AddDNSNames 82 func (d *DefaultLeafRequest) AddDNSNames(dnsNames ...string) LeafRequest { 83 d.dnsNames = d.dnsNames.Union(set.NewStrings(dnsNames...)) 84 return d 85 } 86 87 // AddIPAddresses implements LeafRequest AddIPAddresses 88 func (d *DefaultLeafRequest) AddIPAddresses(ipAddresses ...net.IP) LeafRequest { 89 for _, ipAddress := range ipAddresses { 90 ipStr := ipAddress.String() 91 if _, exists := d.ipAddresses[ipStr]; !exists { 92 d.ipAddresses[ipStr] = ipAddress 93 } 94 } 95 return d 96 } 97 98 // Certificate implements Leaf Certificate 99 func (d *DefaultLeaf) Certificate() *x509.Certificate { 100 return d.certificate 101 } 102 103 // Chain implements Leaf Chain 104 func (d *DefaultLeaf) Chain() []*x509.Certificate { 105 return d.chain 106 } 107 108 // Commit implements Leaf Commit 109 func (d *DefaultLeafRequest) Commit() (Leaf, error) { 110 var err error 111 112 if d.signer == nil { 113 d.signer, err = DefaultKeyProfile() 114 if err != nil { 115 return nil, errors.Trace(err) 116 } 117 } 118 119 csr := &x509.CertificateRequest{ 120 DNSNames: d.dnsNames.Values(), 121 IPAddresses: ipAddressMapToSlice(d.ipAddresses), 122 PublicKey: d.signer.Public(), 123 Subject: d.subject, 124 } 125 126 cert, chain, err := d.requestSigner.SignCSR(csr) 127 if err != nil { 128 return nil, errors.Annotate(err, "signing CSR for leaf") 129 } 130 return d.leafMaker(cert, chain, d.signer) 131 } 132 133 // LeafHasDNSNames tests a diven Leaf to see if it contains the supplied DNS 134 // names 135 func LeafHasDNSNames(leaf Leaf, dnsNames []string) bool { 136 certDNSNames := leaf.Certificate().DNSNames 137 if len(certDNSNames) < len(dnsNames) { 138 return false 139 } 140 141 a := make([]string, len(certDNSNames)) 142 copy(a, certDNSNames) 143 sort.Strings(a) 144 sort.Strings(dnsNames) 145 146 for _, name := range dnsNames { 147 index := sort.SearchStrings(a, name) 148 if index == len(a) || a[index] != name { 149 return false 150 } 151 } 152 return true 153 } 154 155 func ipAddressMapToSlice(m map[string]net.IP) []net.IP { 156 rval := make([]net.IP, len(m)) 157 i := 0 158 for _, v := range m { 159 rval[i] = v 160 i = i + 1 161 } 162 return rval 163 } 164 165 // NewDefaultLeaf constructs a new DefaultLeaf for the supplied certificate and 166 // key 167 func NewDefaultLeaf(group string, cert *x509.Certificate, 168 chain []*x509.Certificate, signer crypto.Signer) *DefaultLeaf { 169 170 tlsCert := &tls.Certificate{ 171 Certificate: make([][]byte, len(chain)+1), 172 PrivateKey: signer, 173 Leaf: cert, 174 } 175 tlsCert.Certificate[0] = cert.Raw 176 177 for i, chainCert := range chain { 178 tlsCert.Certificate[i+1] = chainCert.Raw 179 } 180 181 return &DefaultLeaf{ 182 group: group, 183 certificate: cert, 184 chain: chain, 185 signer: signer, 186 tlsCertificate: tlsCert, 187 } 188 } 189 190 // NewDefaultLeafPem constructs a new DefaultLeaf from the supplied PEM data 191 func NewDefaultLeafPem(group string, pemBlock []byte) (*DefaultLeaf, error) { 192 certs, signers, err := UnmarshalPemData(pemBlock) 193 if err != nil { 194 return nil, errors.Trace(err) 195 } 196 197 if len(certs) == 0 { 198 return nil, errors.New("found zero certificates in pem bundle") 199 } 200 if len(signers) != 1 { 201 return nil, errors.New("expected at least one private key in bundle") 202 } 203 if !PublicKeysEqual(signers[0].Public(), certs[0].PublicKey) { 204 return nil, errors.New("public keys of first certificate and key do not match") 205 } 206 207 return NewDefaultLeaf(group, certs[0], certs[1:], signers[0]), nil 208 } 209 210 // NewDefaultLeafRequest create a DefaultLeafRequest object that implements 211 // LeafRequest 212 func NewDefaultLeafRequest(subject pkix.Name, 213 requestSigner CertificateRequestSigner, maker LeafMaker) *DefaultLeafRequest { 214 return &DefaultLeafRequest{ 215 dnsNames: set.Strings{}, 216 ipAddresses: map[string]net.IP{}, 217 leafMaker: maker, 218 requestSigner: requestSigner, 219 subject: subject, 220 } 221 } 222 223 // NewDefaultLeafRequestWithSigner create a DefaultLeafRequest object that 224 // implements LeafRequest. Takes a default signer to use for all certificate 225 // creation instead of generating a new one. 226 func NewDefaultLeafRequestWithSigner(subject pkix.Name, signer crypto.Signer, 227 requestSigner CertificateRequestSigner, 228 maker LeafMaker) *DefaultLeafRequest { 229 return &DefaultLeafRequest{ 230 dnsNames: set.Strings{}, 231 ipAddresses: map[string]net.IP{}, 232 leafMaker: maker, 233 requestSigner: requestSigner, 234 signer: signer, 235 subject: subject, 236 } 237 } 238 239 // Signer implements Leaf interface Signer 240 func (d *DefaultLeaf) Signer() crypto.Signer { 241 return d.signer 242 } 243 244 // TLSCertificate implements Leaf interface TLSCertificate 245 func (d *DefaultLeaf) TLSCertificate() *tls.Certificate { 246 return d.tlsCertificate 247 } 248 249 // ToPemParts implements Leaf interface ToPemParts 250 func (d *DefaultLeaf) ToPemParts() ([]byte, []byte, error) { 251 certBuf := bytes.Buffer{} 252 err := CertificateToPemWriter(&certBuf, map[string]string{ 253 HeaderLeafGroup: d.group, 254 }, d.Certificate(), d.Chain()...) 255 if err != nil { 256 return nil, nil, errors.Annotate(err, "turning leaf certificate to pem") 257 } 258 259 keyBuf := bytes.Buffer{} 260 err = SignerToPemWriter(&keyBuf, d.Signer()) 261 if err != nil { 262 return nil, nil, errors.Annotate(err, "turning leaf key to pem") 263 } 264 265 return certBuf.Bytes(), keyBuf.Bytes(), nil 266 }