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  }