github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/container/lxd/certificate.go (about)

     1  // Copyright 2018 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package lxd
     5  
     6  import (
     7  	"crypto/sha256"
     8  	"crypto/x509"
     9  	"encoding/base64"
    10  	"encoding/pem"
    11  	"fmt"
    12  	"io"
    13  
    14  	"github.com/canonical/lxd/shared/api"
    15  	"github.com/juju/errors"
    16  )
    17  
    18  // Certificate holds the information for a single certificate that a client may
    19  // use to connect to a remote server.
    20  type Certificate struct {
    21  	// Name is the name that LXD will use for the cert.
    22  	Name string
    23  	// CertPEM is the PEM-encoded x.509 cert.
    24  	CertPEM []byte
    25  	// KeyPEM is the PEM-encoded x.509 private key.
    26  	KeyPEM []byte
    27  }
    28  
    29  // GenerateClientCertificate creates and returns a new certificate for client
    30  // communication with an LXD server.
    31  func GenerateClientCertificate() (*Certificate, error) {
    32  	cert, key, err := GenerateMemCert(true, true)
    33  	if err != nil {
    34  		return nil, errors.Trace(err)
    35  	}
    36  	return NewCertificate(cert, key), nil
    37  }
    38  
    39  // NewCertificate creates a new Certificate for the given cert and key.
    40  func NewCertificate(certPEM, keyPEM []byte) *Certificate {
    41  	return &Certificate{
    42  		CertPEM: certPEM,
    43  		KeyPEM:  keyPEM,
    44  	}
    45  }
    46  
    47  // Validate ensures that the cert is valid.
    48  func (c *Certificate) Validate() error {
    49  	if len(c.CertPEM) == 0 {
    50  		return errors.NotValidf("missing cert PEM")
    51  	}
    52  	if len(c.KeyPEM) == 0 {
    53  		return errors.NotValidf("missing key PEM")
    54  	}
    55  	return nil
    56  }
    57  
    58  // WriteCertPEM writes the cert's x.509 PEM data to the given writer.
    59  func (c *Certificate) WriteCertPEM(out io.Writer) error {
    60  	_, err := out.Write(c.CertPEM)
    61  	return errors.Trace(err)
    62  }
    63  
    64  // WriteKeyPEM writes the key's x.509 PEM data to the given writer.
    65  func (c *Certificate) WriteKeyPEM(out io.Writer) error {
    66  	_, err := out.Write(c.KeyPEM)
    67  	return errors.Trace(err)
    68  }
    69  
    70  // Fingerprint returns the cert's LXD fingerprint.
    71  func (c *Certificate) Fingerprint() (string, error) {
    72  	x509Cert, err := c.X509()
    73  	if err != nil {
    74  		return "", errors.Trace(err)
    75  	}
    76  	data := sha256.Sum256(x509Cert.Raw)
    77  	return fmt.Sprintf("%x", data), nil
    78  }
    79  
    80  // X509 returns the x.509 certificate.
    81  func (c *Certificate) X509() (*x509.Certificate, error) {
    82  	block, _ := pem.Decode(c.CertPEM)
    83  	if block == nil {
    84  		return nil, errors.Errorf("invalid cert PEM (%d bytes)", len(c.CertPEM))
    85  	}
    86  
    87  	x509Cert, err := x509.ParseCertificate(block.Bytes)
    88  	if err != nil {
    89  		return nil, errors.Trace(err)
    90  	}
    91  	return x509Cert, nil
    92  }
    93  
    94  // AsCreateRequest creates a payload for the LXD API, suitable for posting the
    95  // client certificate to an LXD server.
    96  func (c *Certificate) AsCreateRequest() (api.CertificatesPost, error) {
    97  	block, _ := pem.Decode(c.CertPEM)
    98  	if block == nil {
    99  		return api.CertificatesPost{}, errors.New("failed to decode certificate PEM")
   100  	}
   101  
   102  	return api.CertificatesPost{
   103  		CertificatePut: api.CertificatePut{
   104  			Name:        c.Name,
   105  			Type:        "client",
   106  			Certificate: base64.StdEncoding.EncodeToString(block.Bytes),
   107  		},
   108  	}, nil
   109  }