func (c *Client) newRequest(
	ctx context.Context,
	method, endpoint string,
	contentType, transferEncoding string,
	accepts string,
	body io.Reader,
) (*http.Request, error) {
	req, err := http.NewRequestWithContext(ctx, method, c.uri(endpoint), body)
	if err != nil {
		return nil, fmt.Errorf("failed to make new HTTP request: %w", err)
	}
	req.Close = c.DisableKeepAlives
	req.Header.Set(userAgentHeader, userAgent)
	if accepts != "" {
		req.Header.Set(acceptHeader, accepts)
	}
	if contentType != "" {
		req.Header.Set(contentTypeHeader, contentType)
	}
	if transferEncoding != "" {
		req.Header.Set(transferEncodingHeader, transferEncoding)
	}
	if c.HostHeader != "" {
		req.Host = c.HostHeader
	}
	if c.Username != "" {
		req.SetBasicAuth(c.Username, c.Password)
	}
	for k, v := range c.AdditionalHeaders {
		req.Header.Add(k, v)
	}
	return req, err
}
newRequest builds an HTTP request for an EST operation.
package est
import (
	"bytes"
	"context"
	"crypto/tls"
	"crypto/x509"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"mime"
	"mime/multipart"
	"net/http"
	"strconv"
	"strings"
	"time"
)
type Client struct {
	Host string
	AdditionalPathSegment string
	ExplicitAnchor *x509.CertPool
	ImplicitAnchor *x509.CertPool
	Certificates []*x509.Certificate
	PrivateKey interface{}
	AdditionalHeaders map[string]string
	HostHeader string
	Username string
	Password string
	DisableKeepAlives bool
	InsecureSkipVerify bool
}
const (
	estVersion = "v1.0.6"
	userAgent  = "GlobalSign EST Client " + estVersion + " github.com/globalsign/est"
)
func (c *Client) CACerts(ctx context.Context) ([]*x509.Certificate, error) {
	req, err := c.newRequest(ctx, http.MethodGet, cacertsEndpoint, "", "", mimeTypePKCS7, nil)
	if err != nil {
		return nil, err
	}
	resp, err := c.makeHTTPClient().Do(req)
	if err != nil {
		return nil, fmt.Errorf("failed to execute HTTP request: %w", err)
	}
	defer consumeAndClose(resp.Body)
	if err := checkResponseError(resp); err != nil {
		return nil, err
	}
	if err := verifyResponseType(resp, mimeTypePKCS7, encodingTypeBase64); err != nil {
		return nil, err
	}
	return readCertsResponse(resp.Body)
}
func (c *Client) CSRAttrs(ctx context.Context) (CSRAttrs, error) {
	req, err := c.newRequest(ctx, http.MethodGet, csrattrsEndpoint, "", "", mimeTypeCSRAttrs, nil)
	if err != nil {
		return CSRAttrs{}, err
	}
	resp, err := c.makeHTTPClient().Do(req)
	if err != nil {
		return CSRAttrs{}, fmt.Errorf("failed to execute HTTP request: %w", err)
	}
	defer consumeAndClose(resp.Body)
	if err := checkResponseError(resp); err != nil {
		var estErr Error
		if errors.As(err, &estErr); estErr.StatusCode() == http.StatusNotFound ||
			estErr.StatusCode() == http.StatusNoContent {
			return CSRAttrs{}, nil
		}
		return CSRAttrs{}, err
	}
	if err := verifyResponseType(resp, mimeTypeCSRAttrs, encodingTypeBase64); err != nil {
		return CSRAttrs{}, err
	}
	return readCSRAttrsResponse(resp.Body)
}
func (c *Client) Enroll(ctx context.Context, r *x509.CertificateRequest) (*x509.Certificate, error) {
	return c.enrollCommon(ctx, r, false)
}
func (c *Client) Reenroll(ctx context.Context, r *x509.CertificateRequest) (*x509.Certificate, error) {
	return c.enrollCommon(ctx, r, true)
}
func (c *Client) enrollCommon(ctx context.Context, r *x509.CertificateRequest, renew bool) (*x509.Certificate, error) {
	reqBody := ioutil.NopCloser(bytes.NewBuffer(base64Encode(r.Raw)))
	var endpoint = enrollEndpoint
	if renew {
		endpoint = reenrollEndpoint
	}
	req, err := c.newRequest(ctx, http.MethodPost, endpoint, mimeTypePKCS10, encodingTypeBase64, mimeTypePKCS7, reqBody)
	if err != nil {
		return nil, err
	}
	resp, err := c.makeHTTPClient().Do(req)
	if err != nil {
		return nil, fmt.Errorf("failed to execute HTTP request: %w", err)
	}
	defer consumeAndClose(resp.Body)
	if err := checkResponseError(resp); err != nil {
		return nil, err
	}
	if err := verifyResponseType(resp, mimeTypePKCS7, encodingTypeBase64); err != nil {
		return nil, err
	}
	return readCertResponse(resp.Body)
}
func (c *Client) ServerKeyGen(ctx context.Context, r *x509.CertificateRequest) (*x509.Certificate, []byte, error) {
	reqBody := ioutil.NopCloser(bytes.NewBuffer(base64Encode(r.Raw)))
	req, err := c.newRequest(ctx, http.MethodPost, serverkeygenEndpoint,
		mimeTypePKCS10, encodingTypeBase64, mimeTypeMultipart, reqBody)
	if err != nil {
		return nil, nil, err
	}
	resp, err := c.makeHTTPClient().Do(req)
	if err != nil {
		return nil, nil, fmt.Errorf("failed to execute HTTP request: %w", err)
	}
	defer consumeAndClose(resp.Body)
	if err := checkResponseError(resp); err != nil {
		return nil, nil, err
	}
	mediaType, params, err := mime.ParseMediaType(resp.Header.Get(contentTypeHeader))
	if err != nil {
		return nil, nil, fmt.Errorf("missing or malformed %s header: %w", contentTypeHeader, err)
	} else if !strings.HasPrefix(mediaType, mimeTypeMultipart) {
		return nil, nil, fmt.Errorf("unexpected %s: %s", contentTypeHeader, mediaType)
	}
	mpr := multipart.NewReader(resp.Body, params[mimeParamBoundary])
	var cert *x509.Certificate
	var key []byte
	var numParts = 2
	for i := 1; ; i++ {
		part, err := mpr.NextPart()
		if err == io.EOF {
			break
		} else if err != nil {
			return nil, nil, fmt.Errorf("failed to read HTTP response part: %w", err)
		}
		defer part.Close()
		if i > numParts {
			return nil, nil, fmt.Errorf("more than %d parts in HTTP response", numParts)
		}
		if ce := part.Header.Get(transferEncodingHeader); ce == "" {
			return nil, nil, fmt.Errorf("missing %s header", transferEncodingHeader)
		} else if strings.ToUpper(ce) != strings.ToUpper(encodingTypeBase64) {
			return nil, nil, fmt.Errorf("unexpected %s: %s", transferEncodingHeader, ce)
		}
		mediaType, params, err := mime.ParseMediaType(part.Header.Get(contentTypeHeader))
		if err != nil {
			return nil, nil, fmt.Errorf("missing or malformed %s header: %w", contentTypeHeader, err)
		}
		switch {
		case strings.HasPrefix(mediaType, mimeTypePKCS8):
			key, err = readAllBase64Response(part)
			if err != nil {
				return nil, nil, fmt.Errorf("failed to read HTTP response part: %w", err)
			}
		case strings.HasPrefix(mediaType, mimeTypePKCS7):
			t := params[mimeParamSMIMEType]
			switch t {
			case paramValueGenKey:
				key, err = readAllBase64Response(part)
				if err != nil {
					return nil, nil, fmt.Errorf("failed to read HTTP response part: %w", err)
				}
			case paramValueCertsOnly:
				cert, err = readCertResponse(part)
				if err != nil {
					return nil, nil, err
				}
			default:
				return nil, nil, fmt.Errorf("unexpected %s: %s", mimeParamSMIMEType, t)
			}
		default:
			return nil, nil, fmt.Errorf("unexpected %s: %s", contentTypeHeader, mediaType)
		}
	}
	if cert == nil {
		return nil, nil, errors.New("no certificate returned")
	} else if key == nil {
		return nil, nil, errors.New("no private key returned")
	}
	return cert, key, nil
}
func (c *Client) TPMEnroll(
	ctx context.Context,
	r *x509.CertificateRequest,
	ekCerts []*x509.Certificate,
	ekPub []byte,
	akPub []byte,
) ([]byte, []byte, []byte, error) {
	buf, contentType, err := encodeMultiPart(
		tpmEnrollBoundary,
		[]multipartPart{
			{contentType: mimeTypePKCS10, data: r},
			{contentType: mimeTypePKCS7CertsOnly, data: ekCerts},
			{contentType: mimeTypeOctetStream, data: ekPub},
			{contentType: mimeTypeOctetStream, data: akPub},
		},
	)
	if err != nil {
		return nil, nil, nil, err
	}
	reqBody := ioutil.NopCloser(buf)
	req, err := c.newRequest(ctx, http.MethodPost, tpmenrollEndpoint,
		contentType, "", mimeTypeMultipart, reqBody)
	if err != nil {
		return nil, nil, nil, err
	}
	resp, err := c.makeHTTPClient().Do(req)
	if err != nil {
		return nil, nil, nil, fmt.Errorf("failed to execute HTTP request: %w", err)
	}
	defer consumeAndClose(resp.Body)
	if err := checkResponseError(resp); err != nil {
		return nil, nil, nil, err
	}
	mediaType, params, err := mime.ParseMediaType(resp.Header.Get(contentTypeHeader))
	if err != nil {
		return nil, nil, nil, fmt.Errorf("missing or malformed %s header: %w", contentTypeHeader, err)
	} else if !strings.HasPrefix(mediaType, mimeTypeMultipart) {
		return nil, nil, nil, fmt.Errorf("unexpected %s: %s", contentTypeHeader, mediaType)
	}
	mpr := multipart.NewReader(resp.Body, params[mimeParamBoundary])
	var credBlob []byte
	var encSeed []byte
	var cred []byte
	var parts = []struct {
		ctype string
		data  *[]byte
	}{
		{mimeTypeOctetStream, &credBlob},
		{mimeTypeOctetStream, &encSeed},
		{mimeTypePKCS7, &cred},
	}
	var numParts = 3
	for i := 1; ; i++ {
		part, err := mpr.NextPart()
		if err == io.EOF {
			break
		} else if err != nil {
			return nil, nil, nil, fmt.Errorf("failed to get next HTTP response part: %w", err)
		}
		defer part.Close()
		if i > numParts {
			return nil, nil, nil, fmt.Errorf("more than %d parts in HTTP response", numParts)
		}
		if err := verifyPartTypeResponse(part, parts[i-1].ctype, encodingTypeBase64); err != nil {
			return nil, nil, nil, err
		}
		*parts[i-1].data, err = readAllBase64Response(part)
		if err != nil {
			return nil, nil, nil, fmt.Errorf("failed to read HTTP response part: %w", err)
		}
	}
	return credBlob, encSeed, cred, nil
}