certificate.go raw

   1  package api
   2  
   3  import (
   4  	"bytes"
   5  	"encoding/pem"
   6  	"errors"
   7  	"io"
   8  	"net/http"
   9  
  10  	"github.com/go-acme/lego/v4/acme"
  11  )
  12  
  13  // maxBodySize is the maximum size of body that we will read.
  14  const maxBodySize = 1024 * 1024
  15  
  16  type CertificateService service
  17  
  18  // Get Returns the certificate and the issuer certificate.
  19  // 'bundle' is only applied if the issuer is provided by the 'up' link.
  20  func (c *CertificateService) Get(certURL string, bundle bool) ([]byte, []byte, error) {
  21  	cert, _, err := c.get(certURL, bundle)
  22  	if err != nil {
  23  		return nil, nil, err
  24  	}
  25  
  26  	return cert.Cert, cert.Issuer, nil
  27  }
  28  
  29  // GetAll the certificates and the alternate certificates.
  30  // bundle' is only applied if the issuer is provided by the 'up' link.
  31  func (c *CertificateService) GetAll(certURL string, bundle bool) (map[string]*acme.RawCertificate, error) {
  32  	cert, headers, err := c.get(certURL, bundle)
  33  	if err != nil {
  34  		return nil, err
  35  	}
  36  
  37  	certs := map[string]*acme.RawCertificate{certURL: cert}
  38  
  39  	// URLs of "alternate" link relation
  40  	// - https://www.rfc-editor.org/rfc/rfc8555.html#section-7.4.2
  41  	alts := getLinks(headers, "alternate")
  42  
  43  	for _, alt := range alts {
  44  		altCert, _, err := c.get(alt, bundle)
  45  		if err != nil {
  46  			return nil, err
  47  		}
  48  
  49  		certs[alt] = altCert
  50  	}
  51  
  52  	return certs, nil
  53  }
  54  
  55  // Revoke Revokes a certificate.
  56  func (c *CertificateService) Revoke(req acme.RevokeCertMessage) error {
  57  	_, err := c.core.post(c.core.GetDirectory().RevokeCertURL, req, nil)
  58  	return err
  59  }
  60  
  61  // get Returns the certificate and the "up" link.
  62  func (c *CertificateService) get(certURL string, bundle bool) (*acme.RawCertificate, http.Header, error) {
  63  	if certURL == "" {
  64  		return nil, nil, errors.New("certificate[get]: empty URL")
  65  	}
  66  
  67  	resp, err := c.core.postAsGet(certURL, nil)
  68  	if err != nil {
  69  		return nil, nil, err
  70  	}
  71  
  72  	data, err := io.ReadAll(http.MaxBytesReader(nil, resp.Body, maxBodySize))
  73  	if err != nil {
  74  		return nil, resp.Header, err
  75  	}
  76  
  77  	cert := c.getCertificateChain(data, bundle)
  78  
  79  	return cert, resp.Header, err
  80  }
  81  
  82  // getCertificateChain Returns the certificate and the issuer certificate.
  83  func (c *CertificateService) getCertificateChain(cert []byte, bundle bool) *acme.RawCertificate {
  84  	// Get issuerCert from bundled response from Let's Encrypt
  85  	// See https://community.letsencrypt.org/t/acme-v2-no-up-link-in-response/64962
  86  	_, issuer := pem.Decode(cert)
  87  
  88  	// If bundle is false, we want to return a single certificate.
  89  	// To do this, we remove the issuer cert(s) from the issued cert.
  90  	if !bundle {
  91  		cert = bytes.TrimSuffix(cert, issuer)
  92  	}
  93  
  94  	return &acme.RawCertificate{Cert: cert, Issuer: issuer}
  95  }
  96