github.com/Venafi/vcert/v5@v5.10.2/pkg/venafi/cloud/cloud.go (about)

     1  /*
     2   * Copyright 2018 Venafi, Inc.
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *  http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  package cloud
    18  
    19  import (
    20  	"bytes"
    21  	// nolint:gosec // we only use it for getting the certificate thumbprint / fingerprint
    22  	//TODO: although doesn't oppose a risk, we need to figure out a better to do this process so we can remove this library
    23  	"crypto/sha1"
    24  	"crypto/tls"
    25  	"encoding/json"
    26  	"fmt"
    27  	"io"
    28  	"log"
    29  	"net"
    30  	"net/http"
    31  	"net/url"
    32  	"sort"
    33  	"strconv"
    34  	"strings"
    35  	"time"
    36  
    37  	"github.com/go-http-utils/headers"
    38  
    39  	"github.com/Venafi/vcert/v5/pkg/certificate"
    40  	"github.com/Venafi/vcert/v5/pkg/endpoint"
    41  	"github.com/Venafi/vcert/v5/pkg/policy"
    42  	"github.com/Venafi/vcert/v5/pkg/util"
    43  	"github.com/Venafi/vcert/v5/pkg/verror"
    44  )
    45  
    46  type apiKey struct {
    47  	Key                     string    `json:"key,omitempty"`
    48  	UserID                  string    `json:"userId,omitempty"`
    49  	Username                string    `json:"username,omitempty"`
    50  	CompanyID               string    `json:"companyId,omitempty"`
    51  	APITypes                []string  `json:"apitypes,omitempty"`
    52  	APIVersion              string    `json:"apiVersion,omitempty"`
    53  	APIKeyStatus            string    `json:"apiKeyStatus,omitempty"`
    54  	CreationDateString      string    `json:"creationDate,omitempty"`
    55  	CreationDate            time.Time `json:"-"`
    56  	ValidityStartDateString string    `json:"validityStartDate,omitempty"`
    57  	ValidityStartDate       time.Time `json:"-"`
    58  	ValidityEndDateString   string    `json:"validityEndDate,omitempty"`
    59  	ValidityEndDate         time.Time `json:"-"`
    60  }
    61  
    62  type userDetails struct {
    63  	User    *user    `json:"user,omitempty"`
    64  	Company *company `json:"company,omitempty"`
    65  	APIKey  *apiKey  `json:"apiKey,omitempty"`
    66  }
    67  
    68  type OwnerType int64
    69  
    70  const (
    71  	UserType OwnerType = iota
    72  	TeamType
    73  )
    74  
    75  func (o OwnerType) String() string {
    76  	switch o {
    77  	case UserType:
    78  		return "USER"
    79  	case TeamType:
    80  		return "TEAM"
    81  	}
    82  	return "unknown"
    83  }
    84  
    85  type certificateRequestResponse struct {
    86  	CertificateRequests []certificateRequestResponseData `json:"certificateRequests,omitempty"`
    87  }
    88  
    89  type certificateRequestResponseData struct {
    90  	ID                 string    `json:"id,omitempty"`
    91  	ApplicationId      string    `json:"applicationId,omitempty"`
    92  	TemplateId         string    `json:"certificateIssuingTemplateId,omitempty"`
    93  	Status             string    `json:"status,omitempty"`
    94  	SubjectDN          string    `json:"subjectDN,omitempty"`
    95  	CreationDateString string    `json:"creationDate,omitempty"`
    96  	CreationDate       time.Time `json:"-"`
    97  	CertificateIds     []string  `json:"certificateIds,omitempty"`
    98  }
    99  
   100  type certificateRequestClientInfo struct {
   101  	Type       string `json:"type"`
   102  	Identifier string `json:"identifier"`
   103  }
   104  
   105  type certificateRetireResponse struct {
   106  	Count        int           `count:"id,omitempty"`
   107  	Certificates []Certificate `json:"certificates,omitempty"`
   108  }
   109  
   110  type certificateRequest struct {
   111  	CSR                      string                       `json:"certificateSigningRequest,omitempty"`
   112  	ApplicationId            string                       `json:"applicationId,omitempty"`
   113  	TemplateId               string                       `json:"certificateIssuingTemplateId,omitempty"`
   114  	CertificateOwnerUserId   string                       `json:"certificateOwnerUserId,omitempty"`
   115  	ExistingCertificateId    string                       `json:"existingCertificateId,omitempty"`
   116  	ApiClientInformation     certificateRequestClientInfo `json:"apiClientInformation,omitempty"`
   117  	CertificateUsageMetadata []certificateUsageMetadata   `json:"certificateUsageMetadata,omitempty"`
   118  	ReuseCSR                 bool                         `json:"reuseCSR,omitempty"`
   119  	ValidityPeriod           string                       `json:"validityPeriod,omitempty"`
   120  	IsVaaSGenerated          bool                         `json:"isVaaSGenerated,omitempty"`
   121  	CsrAttributes            CsrAttributes                `json:"csrAttributes,omitempty"`
   122  	ApplicationServerTypeId  string                       `json:"applicationServerTypeId,omitempty"`
   123  }
   124  
   125  type certificateRetireRequest struct {
   126  	CertificateIds []string `json:"certificateIds,omitempty"`
   127  	AddToBlocklist bool     `json:"addToBlocklist,omitempty"`
   128  }
   129  
   130  type CsrAttributes struct {
   131  	CommonName                    *string                        `json:"commonName,omitempty"`
   132  	Organization                  *string                        `json:"organization,omitempty"`
   133  	OrganizationalUnits           []string                       `json:"organizationalUnits,omitempty"`
   134  	Locality                      *string                        `json:"locality,omitempty"`
   135  	State                         *string                        `json:"state,omitempty"`
   136  	Country                       *string                        `json:"country,omitempty"`
   137  	SubjectAlternativeNamesByType *SubjectAlternativeNamesByType `json:"subjectAlternativeNamesByType,omitempty"`
   138  	KeyTypeParameters             *KeyTypeParameters             `json:"keyTypeParameters,omitempty"`
   139  }
   140  
   141  type KeyTypeParameters struct {
   142  	KeyType   string  `json:"keyType,omitempty"`
   143  	KeyLength *int    `json:"keyLength,omitempty"`
   144  	KeyCurve  *string `json:"keyCurve,omitempty"`
   145  }
   146  
   147  type SubjectAlternativeNamesByType struct {
   148  	DnsNames                   []string `json:"dnsNames,omitempty"`
   149  	IpAddresses                []string `json:"ipAddresses,omitempty"`
   150  	Rfc822Names                []string `json:"rfc822Names,omitempty"`
   151  	UniformResourceIdentifiers []string `json:"uniformResourceIdentifiers,omitempty"`
   152  }
   153  
   154  type KeyStoreRequest struct {
   155  	ExportFormat                  string `json:"exportFormat,omitempty"`
   156  	EncryptedPrivateKeyPassphrase string `json:"encryptedPrivateKeyPassphrase"`
   157  	EncryptedKeystorePassphrase   string `json:"encryptedKeystorePassphrase"`
   158  	CertificateLabel              string `json:"certificateLabel"`
   159  }
   160  
   161  type EdgeEncryptionKey struct {
   162  	Key string `json:"key,omitempty"`
   163  }
   164  
   165  type certificateStatus struct {
   166  	Id                        string                            `json:"id,omitempty"`
   167  	CertificateIdsList        []string                          `json:"certificateIds,omitempty"`
   168  	ApplicationId             string                            `json:"applicationId,omitempty"`
   169  	TemplateId                string                            `json:"certificateIssuingTemplateId,omitempty"`
   170  	Status                    string                            `json:"status,omitempty"`
   171  	ErrorInformation          CertificateStatusErrorInformation `json:"errorInformation,omitempty"`
   172  	CreationDate              string                            `json:"creationDate,omitempty"`
   173  	ModificationDate          string                            `json:"modificationDate,omitempty"`
   174  	CertificateSigningRequest string                            `json:"certificateSigningRequest,omitempty"`
   175  	SubjectDN                 string                            `json:"subjectDN,omitempty"`
   176  }
   177  
   178  type CertificateStatusErrorInformation struct {
   179  	Type    string   `json:"type,omitempty"`
   180  	Code    int      `json:"code,omitempty"`
   181  	Message string   `json:"message,omitempty"`
   182  	Args    []string `json:"args,omitempty"`
   183  }
   184  
   185  type apiClientInformation struct {
   186  	Type       string `json:"type"`
   187  	Identifier string `json:"identifier"`
   188  }
   189  
   190  type certificateUsageMetadata struct {
   191  	AppName            string `json:"appName,omitempty"`
   192  	NodeName           string `json:"nodeName,omitempty"`
   193  	AutomationMetadata string `json:"automationMetadata,omitempty"`
   194  }
   195  
   196  type importRequest struct {
   197  	Certificates []importRequestCertInfo `json:"certificates"`
   198  }
   199  
   200  type importRequestCertInfo struct {
   201  	Certificate              string                     `json:"certificate"`
   202  	IssuerCertificates       []string                   `json:"issuerCertificates,omitempty"`
   203  	ApplicationIds           []string                   `json:"applicationIds"`
   204  	ApiClientInformation     apiClientInformation       `json:"apiClientInformation,omitempty"`
   205  	CertificateUsageMetadata []certificateUsageMetadata `json:"certificateUsageMetadata,omitempty"`
   206  }
   207  
   208  type importResponseCertInfo struct {
   209  	Id                      string               `json:"id"`
   210  	ManagedCertificateId    string               `json:"managedCertificateId"`
   211  	CompanyId               string               `json:"companyId"`
   212  	Fingerprint             string               `json:"fingerprint"`
   213  	CertificateSource       string               `json:"certificateSource"`
   214  	OwnerUserId             string               `json:"ownerUserId"`
   215  	IssuanceZoneId          string               `json:"issuanceZoneId"`
   216  	ValidityStartDateString string               `json:"validityStartDate"`
   217  	ValidityStartDate       time.Time            `json:"-"`
   218  	ValidityEndDateString   string               `json:"validityEndDate"`
   219  	ValidityEndDate         time.Time            `json:"-"`
   220  	ApiClientInformation    apiClientInformation `json:"apiClientInformation,omitempty"`
   221  }
   222  
   223  type importResponse struct {
   224  	CertificateInformations []importResponseCertInfo `json:"certificateInformations"`
   225  }
   226  
   227  type ApplicationDetails struct {
   228  	ApplicationId             string               `json:"id,omitempty"`
   229  	CitAliasToIdMap           map[string]string    `json:"certificateIssuingTemplateAliasIdMap,omitempty"`
   230  	CompanyId                 string               `json:"companyId,omitempty"`
   231  	Name                      string               `json:"name,omitempty"`
   232  	Description               string               `json:"description,omitempty"`
   233  	OwnerIdType               []policy.OwnerIdType `json:"ownerIdsAndTypes,omitempty"`
   234  	InternalFqDns             []string             `json:"internalFqDns,omitempty"`
   235  	ExternalIpRanges          []string             `json:"externalIpRanges,omitempty"`
   236  	InternalIpRanges          []string             `json:"internalIpRanges,omitempty"`
   237  	InternalPorts             []string             `json:"internalPorts,omitempty"`
   238  	FullyQualifiedDomainNames []string             `json:"fullyQualifiedDomainNames,omitempty"`
   239  	IpRanges                  []string             `json:"ipRanges,omitempty"`
   240  	Ports                     []string             `json:"ports,omitempty"`
   241  	FqDns                     []string             `json:"fqDns,omitempty"`
   242  }
   243  
   244  // GenerateRequest generates a CertificateRequest based on the zone configuration, and returns the request along with the private key.
   245  func (c *Connector) GenerateRequest(config *endpoint.ZoneConfiguration, req *certificate.Request) (err error) {
   246  	switch req.CsrOrigin {
   247  	case certificate.LocalGeneratedCSR:
   248  		if config == nil {
   249  			config, err = c.ReadZoneConfiguration()
   250  			if err != nil {
   251  				return fmt.Errorf("could not read zone configuration: %w", err)
   252  			}
   253  		}
   254  		config.UpdateCertificateRequest(req)
   255  		if err := req.GeneratePrivateKey(); err != nil {
   256  			return err
   257  		}
   258  		err = req.GenerateCSR()
   259  		return
   260  	case certificate.UserProvidedCSR:
   261  		if len(req.GetCSR()) == 0 {
   262  			return fmt.Errorf("%w: CSR was supposed to be provided by user, but it's empty", verror.UserDataError)
   263  		}
   264  		return nil
   265  
   266  	case certificate.ServiceGeneratedCSR:
   267  		if req.KeyType == certificate.KeyTypeED25519 {
   268  			return fmt.Errorf("%w: ED25519 keys are not yet supported for Service Generated CSR", verror.UserDataError)
   269  		}
   270  		return nil
   271  
   272  	default:
   273  		return fmt.Errorf("%w: unrecognised req.CsrOrigin %v", verror.UserDataError, req.CsrOrigin)
   274  	}
   275  }
   276  
   277  func (c *Connector) getURL(resource urlResource) string {
   278  	return fmt.Sprintf("%s%s", c.baseURL, resource)
   279  }
   280  
   281  func (c *Connector) getHTTPClient() *http.Client {
   282  	if c.client != nil {
   283  		return c.client
   284  	}
   285  	var netTransport = &http.Transport{
   286  		Proxy: http.ProxyFromEnvironment,
   287  		DialContext: (&net.Dialer{
   288  			Timeout:   30 * time.Second,
   289  			KeepAlive: 30 * time.Second,
   290  		}).DialContext,
   291  		MaxIdleConns:          100,
   292  		IdleConnTimeout:       90 * time.Second,
   293  		TLSHandshakeTimeout:   10 * time.Second,
   294  		ExpectContinueTimeout: 1 * time.Second,
   295  	}
   296  	tlsConfig := http.DefaultTransport.(*http.Transport).TLSClientConfig
   297  	/* #nosec */
   298  	if c.trust != nil {
   299  		if tlsConfig == nil {
   300  			tlsConfig = &tls.Config{
   301  				MinVersion: tls.VersionTLS12,
   302  			}
   303  		} else {
   304  			tlsConfig = tlsConfig.Clone()
   305  		}
   306  		tlsConfig.RootCAs = c.trust
   307  		netTransport.TLSClientConfig = tlsConfig
   308  	}
   309  
   310  	c.client = &http.Client{
   311  		Timeout:   time.Second * 30,
   312  		Transport: netTransport,
   313  	}
   314  	return c.client
   315  }
   316  
   317  func (c *Connector) request(method string, url string, data interface{}, authNotRequired ...bool) (statusCode int, statusText string, body []byte, err error) {
   318  	if (c.accessToken == "" && c.user == nil) || (c.user != nil && c.user.Company == nil) {
   319  		if !(len(authNotRequired) == 1 && authNotRequired[0]) {
   320  			err = fmt.Errorf("%w: must be autheticated to make requests to TLSPC API", verror.VcertError)
   321  			return
   322  		}
   323  	}
   324  
   325  	var payload io.Reader
   326  	var b []byte
   327  	if method == http.MethodPost || method == http.MethodPut {
   328  		b, _ = json.Marshal(data)
   329  		payload = bytes.NewReader(b)
   330  	}
   331  
   332  	r, err := http.NewRequest(method, url, payload)
   333  	if err != nil {
   334  		err = fmt.Errorf("%w: %v", verror.VcertError, err)
   335  		return
   336  	}
   337  
   338  	r.Header.Set(headers.UserAgent, c.userAgent)
   339  	if c.accessToken != "" {
   340  		r.Header.Add(headers.Authorization, fmt.Sprintf("%s %s", util.OauthTokenType, c.accessToken))
   341  	} else if c.apiKey != "" {
   342  		r.Header.Add(util.HeaderTpplApikey, c.apiKey)
   343  	}
   344  
   345  	if method == http.MethodPost || method == http.MethodPut {
   346  		r.Header.Add(headers.Accept, "application/json")
   347  		r.Header.Add(headers.ContentType, "application/json")
   348  	} else {
   349  		r.Header.Add(headers.Accept, "*/*")
   350  	}
   351  	r.Header.Add(headers.CacheControl, "no-cache")
   352  
   353  	var httpClient = c.getHTTPClient()
   354  
   355  	res, err := httpClient.Do(r)
   356  	if err != nil {
   357  		err = fmt.Errorf("%w: %v", verror.ServerUnavailableError, err)
   358  		return
   359  	}
   360  	statusCode = res.StatusCode
   361  	statusText = res.Status
   362  
   363  	defer res.Body.Close()
   364  	body, err = io.ReadAll(res.Body)
   365  	if err != nil {
   366  		err = fmt.Errorf("%w: %v", verror.ServerError, err)
   367  	}
   368  
   369  	if c.verbose {
   370  		log.Printf("Got %s status for %s %s\n", statusText, method, url)
   371  	}
   372  	return
   373  }
   374  
   375  func parseUserDetailsResult(expectedStatusCode int, httpStatusCode int, httpStatus string, body []byte) (*userDetails, error) {
   376  	if httpStatusCode == expectedStatusCode {
   377  		return parseJSON[userDetails](body, verror.ServerError)
   378  	}
   379  	respErrors, err := parseResponseErrors(body)
   380  	if err != nil {
   381  		// Parsing the error failed, return the original error
   382  		bodyText := strings.TrimSpace(string(body))
   383  		if bodyText == "" {
   384  			return nil, fmt.Errorf("%w: %s", verror.ServerError, httpStatus)
   385  		}
   386  
   387  		return nil, fmt.Errorf("%w: %s, %s", verror.ServerError, httpStatus, bodyText)
   388  	}
   389  	respError := fmt.Sprintf("unexpected status code on Venafi Cloud registration. Status: %s\n", httpStatus)
   390  	for _, e := range respErrors {
   391  		respError += fmt.Sprintf("Error Code: %d Error: %s\n", e.Code, e.Message)
   392  	}
   393  	return nil, fmt.Errorf("%w: %v", verror.ServerError, respError)
   394  }
   395  
   396  func parseUserDetailsResultFromPOST(httpStatusCode int, httpStatus string, body []byte) (*userDetails, error) {
   397  	if httpStatusCode == http.StatusCreated || httpStatusCode == http.StatusAccepted {
   398  		return parseJSON[userDetails](body, verror.ServerError)
   399  	}
   400  	respErrors, err := parseResponseErrors(body)
   401  	if err != nil {
   402  		return nil, err // parseResponseErrors always return verror.ServerError
   403  	}
   404  	respError := fmt.Sprintf("unexpected status code on Venafi Cloud registration. Status: %s\n", httpStatus)
   405  	for _, e := range respErrors {
   406  		respError += fmt.Sprintf("Error Code: %d Error: %s\n", e.Code, e.Message)
   407  	}
   408  	return nil, fmt.Errorf("%w: %v", verror.ServerError, respError)
   409  }
   410  
   411  func parseJSON[T any](b []byte, errorMessage error) (*T, error) {
   412  	var data T
   413  	err := json.Unmarshal(b, &data)
   414  	if err != nil {
   415  		return nil, fmt.Errorf("%w: %v", errorMessage, err)
   416  	}
   417  	return &data, nil
   418  }
   419  
   420  func parseUserByIdResult(expectedStatusCode int, httpStatusCode int, httpStatus string, body []byte) (*user, error) {
   421  	if httpStatusCode == expectedStatusCode {
   422  		return parseJSON[user](body, verror.ServerError)
   423  	}
   424  	respErrors, err := parseResponseErrors(body)
   425  	if err != nil {
   426  		return nil, err // parseResponseErrors always return verror.ServerError
   427  	}
   428  	respError := fmt.Sprintf("unexpected status code on retrieval of user by ID. Status: %s\n", httpStatus)
   429  	for _, e := range respErrors {
   430  		respError += fmt.Sprintf("Error Code: %d Error: %s\n", e.Code, e.Message)
   431  	}
   432  	return nil, fmt.Errorf("%w: %v", verror.ServerError, respError)
   433  }
   434  
   435  func parseUsersByNameResult(expectedStatusCode int, httpStatusCode int, httpStatus string, body []byte) (*users, error) {
   436  	if httpStatusCode == expectedStatusCode {
   437  		return parseJSON[users](body, verror.ServerError)
   438  	}
   439  	respErrors, err := parseResponseErrors(body)
   440  	if err != nil {
   441  		return nil, err // parseResponseErrors always return verror.ServerError
   442  	}
   443  	respError := fmt.Sprintf("unexpected status code on retrieval of users by name. Status: %s\n", httpStatus)
   444  	for _, e := range respErrors {
   445  		respError += fmt.Sprintf("Error Code: %d Error: %s\n", e.Code, e.Message)
   446  	}
   447  	return nil, fmt.Errorf("%w: %v", verror.ServerError, respError)
   448  }
   449  
   450  func parseCertByIdResult(expectedStatusCode int, httpStatusCode int, httpStatus string, body []byte) (*VenafiCertificate, error) {
   451  	if httpStatusCode == expectedStatusCode {
   452  		return parseJSON[VenafiCertificate](body, verror.ServerError)
   453  	}
   454  	respErrors, err := parseResponseErrors(body)
   455  	if err != nil {
   456  		return nil, err // parseResponseErrors always return verror.ServerError
   457  	}
   458  	respError := fmt.Sprintf("unexpected status code on retrieval of certificate by ID. Status: %s\n", httpStatus)
   459  	for _, e := range respErrors {
   460  		respError += fmt.Sprintf("Error Code: %d Error: %s\n", e.Code, e.Message)
   461  	}
   462  	return nil, fmt.Errorf("%w: %v", verror.ServerError, respError)
   463  }
   464  
   465  func parseTeamsResult(expectedStatusCode int, httpStatusCode int, httpStatus string, body []byte) (*teams, error) {
   466  	if httpStatusCode == expectedStatusCode {
   467  		return parseJSON[teams](body, verror.ServerError)
   468  	}
   469  	respErrors, err := parseResponseErrors(body)
   470  	if err != nil {
   471  		return nil, err // parseResponseErrors always return verror.ServerError
   472  	}
   473  	respError := fmt.Sprintf("unexpected status code on retrieval of teams. Status: %s\n", httpStatus)
   474  	for _, e := range respErrors {
   475  		respError += fmt.Sprintf("Error Code: %d Error: %s\n", e.Code, e.Message)
   476  	}
   477  	return nil, fmt.Errorf("%w: %v", verror.ServerError, respError)
   478  }
   479  
   480  func parseZoneConfigurationResult(httpStatusCode int, httpStatus string, body []byte) (*zone, error) {
   481  	switch httpStatusCode {
   482  	case http.StatusOK:
   483  		return parseJSON[zone](body, verror.ServerError)
   484  	case http.StatusBadRequest, http.StatusNotFound:
   485  		return nil, verror.ZoneNotFoundError
   486  	default:
   487  		respErrors, err := parseResponseErrors(body)
   488  		if err != nil {
   489  			return nil, err
   490  		}
   491  
   492  		respError := fmt.Sprintf("Unexpected status code on Venafi Cloud zone read. Status: %s\n", httpStatus)
   493  		for _, e := range respErrors {
   494  			if e.Code == 10051 {
   495  				return nil, verror.ZoneNotFoundError
   496  			}
   497  			respError += fmt.Sprintf("Error Code: %d Error: %s\n", e.Code, e.Message)
   498  		}
   499  		return nil, fmt.Errorf("%w: %v", verror.ServerError, respError)
   500  	}
   501  }
   502  
   503  func parseCertificateTemplateResult(httpStatusCode int, httpStatus string, body []byte) (*certificateTemplate, error) {
   504  	switch httpStatusCode {
   505  	case http.StatusOK:
   506  		return parseJSON[certificateTemplate](body, verror.ServerError)
   507  	case http.StatusBadRequest:
   508  		return nil, verror.ZoneNotFoundError
   509  	case http.StatusUnauthorized:
   510  		return nil, verror.UnauthorizedError
   511  	default:
   512  		respErrors, err := parseResponseErrors(body)
   513  		if err != nil {
   514  			return nil, err
   515  		}
   516  
   517  		respError := fmt.Sprintf("Unexpected status code on Venafi Cloud zone read. Status: %s\n", httpStatus)
   518  		for _, e := range respErrors {
   519  			if e.Code == 10051 {
   520  				return nil, verror.ZoneNotFoundError
   521  			}
   522  			respError += fmt.Sprintf("Error Code: %d Error: %s\n", e.Code, e.Message)
   523  		}
   524  		return nil, fmt.Errorf("%w: %v", verror.ServerError, respError)
   525  	}
   526  }
   527  
   528  func parseCertificateRequestResult(httpStatusCode int, httpStatus string, body []byte) (*certificateRequestResponse, error) {
   529  	switch httpStatusCode {
   530  	case http.StatusCreated:
   531  		return parseJSON[certificateRequestResponse](body, verror.ServerError)
   532  	default:
   533  		respErrors, err := parseResponseErrors(body)
   534  		if err != nil {
   535  			return nil, err
   536  		}
   537  
   538  		respError := fmt.Sprintf("Unexpected status code on Venafi Cloud zone read. Status: %s\n", httpStatus)
   539  		for _, e := range respErrors {
   540  			respError += fmt.Sprintf("Error Code: %d Error: %s\n", e.Code, e.Message)
   541  		}
   542  		return nil, fmt.Errorf("%w: %v", verror.ServerError, respError)
   543  	}
   544  }
   545  
   546  func checkCertificateRetireResults(httpStatusCode int, httpStatus string, body []byte) error {
   547  	switch httpStatusCode {
   548  	case 200:
   549  		resp, err := parseJSON[certificateRetireResponse](body, verror.ServerError)
   550  		if err != nil {
   551  			return err
   552  		} else if resp.Count == 0 {
   553  			return fmt.Errorf("Invalid thumbprint or certificate ID. No certificates were retired")
   554  		} else {
   555  			return nil
   556  		}
   557  	default:
   558  		respErrors, err := parseResponseErrors(body)
   559  		if err != nil {
   560  			return err
   561  		}
   562  
   563  		respError := fmt.Sprintf("Unexpected status code on Venafi Cloud zone read. Status: %s\n", httpStatus)
   564  		for _, e := range respErrors {
   565  			respError += fmt.Sprintf("Error Code: %d Error: %s\n", e.Code, e.Message)
   566  		}
   567  		return fmt.Errorf("%w: %v", verror.ServerError, respError)
   568  	}
   569  }
   570  
   571  func newPEMCollectionFromResponse(data []byte, chainOrder certificate.ChainOption) (*certificate.PEMCollection, error) {
   572  	return certificate.PEMCollectionFromBytes(data, chainOrder)
   573  }
   574  
   575  func certThumbprint(asn1 []byte) string {
   576  	// nolint:gosec // we only use it for getting the certificate thumbprint / fingerprint
   577  	h := sha1.Sum(asn1) // TODO: although doesn't oppose a risk, we need to figure out a better to do this process
   578  	return strings.ToUpper(fmt.Sprintf("%x", h))
   579  }
   580  
   581  func parseApplicationDetailsResult(httpStatusCode int, httpStatus string, body []byte) (*ApplicationDetails, error) {
   582  	switch httpStatusCode {
   583  	case http.StatusOK:
   584  		return parseJSON[ApplicationDetails](body, verror.ServerError)
   585  	case http.StatusBadRequest:
   586  		return nil, verror.ApplicationNotFoundError
   587  	case http.StatusUnauthorized:
   588  		return nil, fmt.Errorf("%w: %s", verror.ServerError, httpStatus)
   589  	default:
   590  		respErrors, err := parseResponseErrors(body)
   591  		if err != nil {
   592  			return nil, err
   593  		}
   594  
   595  		respError := fmt.Sprintf("Unexpected status code on Venafi Cloud application read. Status: %s\n", httpStatus)
   596  		for _, e := range respErrors {
   597  			if e.Code == 10051 {
   598  				return nil, verror.ApplicationNotFoundError
   599  			}
   600  			respError += fmt.Sprintf("Error Code: %d Error: %s\n", e.Code, e.Message)
   601  		}
   602  		return nil, fmt.Errorf("%w: %v", verror.ServerError, respError)
   603  	}
   604  }
   605  
   606  type cloudZone struct {
   607  	zone          string
   608  	appName       string
   609  	templateAlias string
   610  }
   611  
   612  func (z cloudZone) String() string {
   613  	return z.zone
   614  }
   615  
   616  func (z *cloudZone) getApplicationName() string {
   617  	if z.appName == "" {
   618  		err := z.parseZone()
   619  		if err != nil {
   620  			return ""
   621  		}
   622  	}
   623  	return z.appName
   624  }
   625  
   626  func (z *cloudZone) getTemplateAlias() string {
   627  	if z.templateAlias == "" {
   628  		err := z.parseZone()
   629  		if err != nil {
   630  			return ""
   631  		}
   632  	}
   633  	return z.templateAlias
   634  }
   635  
   636  func (z *cloudZone) parseZone() error {
   637  	if z.zone == "" {
   638  		return fmt.Errorf("zone not specified")
   639  	}
   640  
   641  	segments := strings.Split(z.zone, "\\")
   642  	if len(segments) != 2 {
   643  		return fmt.Errorf("invalid zone format")
   644  	}
   645  
   646  	z.appName = segments[0]
   647  	z.templateAlias = segments[1]
   648  
   649  	return nil
   650  }
   651  
   652  func createAppUpdateRequest(applicationDetails *ApplicationDetails) policy.Application {
   653  	request := policy.Application{
   654  		OwnerIdsAndTypes:                     applicationDetails.OwnerIdType,
   655  		Name:                                 applicationDetails.Name,
   656  		Description:                          applicationDetails.Description,
   657  		Fqdns:                                applicationDetails.FqDns,
   658  		InternalFqdns:                        applicationDetails.InternalFqDns,
   659  		InternalIpRanges:                     applicationDetails.InternalIpRanges,
   660  		ExternalIpRanges:                     applicationDetails.ExternalIpRanges,
   661  		InternalPorts:                        applicationDetails.InternalPorts,
   662  		FullyQualifiedDomainNames:            applicationDetails.FullyQualifiedDomainNames,
   663  		IpRanges:                             applicationDetails.IpRanges,
   664  		Ports:                                applicationDetails.Ports,
   665  		CertificateIssuingTemplateAliasIdMap: applicationDetails.CitAliasToIdMap,
   666  	}
   667  
   668  	return request
   669  }
   670  
   671  func getSAN(p *policy.Policy) *policy.SubjectAltNames {
   672  	if p == nil || p.SubjectAltNames == nil {
   673  		san := policy.SubjectAltNames{}
   674  		p.SubjectAltNames = &san
   675  		return &san
   676  	}
   677  	return p.SubjectAltNames
   678  }
   679  
   680  func buildPolicySpecification(cit *certificateTemplate, info *policy.CertificateAuthorityInfo, removeRegex bool) *policy.PolicySpecification {
   681  	if cit == nil {
   682  		return nil
   683  	}
   684  
   685  	var ps policy.PolicySpecification
   686  
   687  	var pol policy.Policy
   688  
   689  	if len(cit.SubjectCNRegexes) > 0 {
   690  		if removeRegex {
   691  			pol.Domains = policy.RemoveRegex(cit.SubjectCNRegexes)
   692  		} else {
   693  			pol.Domains = cit.SubjectCNRegexes
   694  		}
   695  	}
   696  
   697  	wildCard := isWildCard(cit.SubjectCNRegexes)
   698  	pol.WildcardAllowed = &wildCard
   699  
   700  	if len(cit.SANRegexes) > 0 {
   701  		subjectAlt := getSAN(&pol)
   702  		subjectAlt.DnsAllowed = util.GetBooleanRef(true)
   703  	}
   704  
   705  	if len(cit.SanRfc822NameRegexes) > 0 {
   706  		subjectAlt := getSAN(&pol)
   707  		subjectAlt.EmailAllowed = util.GetBooleanRef(true)
   708  	}
   709  
   710  	if len(cit.SanUniformResourceIdentifierRegexes) > 0 {
   711  		subjectAlt := getSAN(&pol)
   712  		protocols := make([]string, 0)
   713  		for _, val := range cit.SanUniformResourceIdentifierRegexes {
   714  			index := strings.Index(val, ")://")
   715  			subStr := val[1:index]
   716  			currProtocols := strings.Split(subStr, "|")
   717  			for _, currentProtocol := range currProtocols {
   718  				if len(protocols) == 0 {
   719  					protocols = append(protocols, currentProtocol)
   720  				} else {
   721  					if !contains(protocols, currentProtocol) {
   722  						protocols = append(protocols, currentProtocol)
   723  					}
   724  				}
   725  			}
   726  		}
   727  		subjectAlt.UriProtocols = protocols
   728  		subjectAlt.UriAllowed = util.GetBooleanRef(true)
   729  	}
   730  
   731  	if len(cit.SanIpAddressRegexes) > 0 {
   732  		subjectAlt := getSAN(&pol)
   733  		subjectAlt.IpAllowed = util.GetBooleanRef(true)
   734  	}
   735  
   736  	// ps.Policy.WildcardAllowed is pending.
   737  	if cit.ValidityPeriod != "" {
   738  		//they have the format P#D
   739  		days := cit.ValidityPeriod[1 : len(cit.ValidityPeriod)-1]
   740  		intDays, _ := strconv.ParseInt(days, 10, 32)
   741  		//ok we have a 32 bits int but we need to convert it just into a "int"
   742  		intVal := int(intDays)
   743  		pol.MaxValidDays = &intVal
   744  	}
   745  	if info != nil {
   746  		ca := fmt.Sprint(info.CAType, "\\", info.CAAccountKey, "\\", info.VendorProductName)
   747  		pol.CertificateAuthority = &ca
   748  	}
   749  
   750  	//subject.
   751  	var subject policy.Subject
   752  
   753  	if len(cit.SubjectORegexes) > 0 {
   754  		subject.Orgs = cit.SubjectORegexes
   755  	} else if cit.SubjectORegexes == nil {
   756  		subject.Orgs = []string{""}
   757  	}
   758  
   759  	if len(cit.SubjectOURegexes) > 0 {
   760  		subject.OrgUnits = cit.SubjectOURegexes
   761  	} else if cit.SubjectOURegexes == nil {
   762  		subject.OrgUnits = []string{""}
   763  	}
   764  
   765  	if len(cit.SubjectLRegexes) > 0 {
   766  		subject.Localities = cit.SubjectLRegexes
   767  	} else if cit.SubjectLRegexes == nil {
   768  		subject.Localities = []string{""}
   769  	}
   770  
   771  	if len(cit.SubjectSTRegexes) > 0 {
   772  		subject.States = cit.SubjectSTRegexes
   773  	} else if cit.SubjectSTRegexes == nil {
   774  		subject.States = []string{""}
   775  	}
   776  
   777  	if len(cit.SubjectCValues) > 0 {
   778  		subject.Countries = cit.SubjectCValues
   779  	} else if cit.SubjectCValues == nil {
   780  		subject.Countries = []string{""}
   781  	}
   782  
   783  	pol.Subject = &subject
   784  
   785  	//key pair
   786  	var keyPair policy.KeyPair
   787  	shouldCreateKeyPair := false
   788  	if len(cit.KeyTypes) > 0 {
   789  		var keyTypes []string
   790  		var keySizes []int
   791  		var ellipticCurves []string
   792  
   793  		for _, allowedKT := range cit.KeyTypes {
   794  			keyType := string(allowedKT.KeyType)
   795  			keyLengths := allowedKT.KeyLengths
   796  			ecKeys := allowedKT.KeyCurves
   797  
   798  			keyTypes = append(keyTypes, keyType)
   799  
   800  			if len(keyLengths) > 0 {
   801  				keySizes = append(keySizes, keyLengths...)
   802  			}
   803  
   804  			if len(ecKeys) > 0 {
   805  				ellipticCurves = append(ellipticCurves, ecKeys...)
   806  			}
   807  
   808  		}
   809  		shouldCreateKeyPair = true
   810  		keyPair.KeyTypes = keyTypes
   811  		if len(keySizes) > 0 {
   812  			keyPair.RsaKeySizes = keySizes
   813  		}
   814  
   815  		if len(ellipticCurves) > 0 {
   816  			keyPair.EllipticCurves = ellipticCurves
   817  		}
   818  	}
   819  
   820  	if cit.KeyGeneratedByVenafiAllowed && cit.CsrUploadAllowed {
   821  		keyPair.ServiceGenerated = nil
   822  	} else if cit.KeyGeneratedByVenafiAllowed {
   823  		keyPair.ServiceGenerated = &cit.KeyGeneratedByVenafiAllowed
   824  		shouldCreateKeyPair = true
   825  	} else if cit.CsrUploadAllowed {
   826  		falseVal := false
   827  		keyPair.ServiceGenerated = &falseVal
   828  		shouldCreateKeyPair = true
   829  	}
   830  
   831  	if shouldCreateKeyPair {
   832  		pol.KeyPair = &keyPair
   833  		pol.KeyPair.ReuseAllowed = &cit.KeyReuse
   834  	}
   835  
   836  	ps.Policy = &pol
   837  
   838  	//build defaults.
   839  	var defaultSub policy.DefaultSubject
   840  	shouldCreateDeFaultSub := false
   841  	if cit.RecommendedSettings.SubjectOValue != "" {
   842  		defaultSub.Org = &cit.RecommendedSettings.SubjectOValue
   843  		shouldCreateDeFaultSub = true
   844  	}
   845  
   846  	if cit.RecommendedSettings.SubjectOUValue != "" {
   847  		defaultSub.OrgUnits = []string{cit.RecommendedSettings.SubjectOUValue}
   848  		shouldCreateDeFaultSub = true
   849  	}
   850  
   851  	if cit.RecommendedSettings.SubjectCValue != "" {
   852  		defaultSub.Country = &cit.RecommendedSettings.SubjectCValue
   853  		shouldCreateDeFaultSub = true
   854  	}
   855  
   856  	if cit.RecommendedSettings.SubjectSTValue != "" {
   857  		defaultSub.State = &cit.RecommendedSettings.SubjectSTValue
   858  		shouldCreateDeFaultSub = true
   859  	}
   860  
   861  	if cit.RecommendedSettings.SubjectLValue != "" {
   862  		defaultSub.Locality = &cit.RecommendedSettings.SubjectLValue
   863  		shouldCreateDeFaultSub = true
   864  	}
   865  
   866  	if shouldCreateDeFaultSub {
   867  		if ps.Default == nil {
   868  			ps.Default = &policy.Default{}
   869  		}
   870  		ps.Default.Subject = &defaultSub
   871  	}
   872  
   873  	//default key type
   874  	var defaultKP policy.DefaultKeyPair
   875  	shouldCreateDefaultKeyPAir := false
   876  
   877  	if cit.RecommendedSettings.Key.Type != "" {
   878  		defaultKP.KeyType = &cit.RecommendedSettings.Key.Type
   879  		shouldCreateDefaultKeyPAir = true
   880  	}
   881  
   882  	if cit.RecommendedSettings.Key.Length > 0 {
   883  		defaultKP.RsaKeySize = &cit.RecommendedSettings.Key.Length
   884  		shouldCreateDefaultKeyPAir = true
   885  	}
   886  
   887  	if cit.RecommendedSettings.Key.Curve != "" {
   888  		defaultKP.EllipticCurve = &cit.RecommendedSettings.Key.Curve
   889  		shouldCreateDefaultKeyPAir = true
   890  	}
   891  
   892  	if shouldCreateDefaultKeyPAir {
   893  		if ps.Default == nil {
   894  			ps.Default = &policy.Default{}
   895  		}
   896  		ps.Default.KeyPair = &defaultKP
   897  	}
   898  
   899  	return &ps
   900  }
   901  
   902  func contains(values []string, toSearch string) bool {
   903  	copiedValues := make([]string, len(values))
   904  	copy(copiedValues, values)
   905  	sort.Strings(copiedValues)
   906  
   907  	return binarySearch(copiedValues, toSearch) >= 0
   908  }
   909  
   910  func binarySearch(values []string, toSearch string) int {
   911  	length := len(values) - 1
   912  	minimum := 0
   913  	for minimum <= length {
   914  		mid := length - (length-minimum)/2
   915  		if strings.Compare(toSearch, values[mid]) > 0 {
   916  			minimum = mid + 1
   917  		} else if strings.Compare(toSearch, values[mid]) < 0 {
   918  			length = mid - 1
   919  		} else {
   920  			return mid
   921  		}
   922  	}
   923  	return -1
   924  }
   925  
   926  func parseCitResult(expectedStatusCode int, httpStatusCode int, httpStatus string, body []byte) (*certificateTemplate, error) {
   927  	if httpStatusCode == expectedStatusCode {
   928  		return parseCitDetailsData(body, httpStatusCode)
   929  	}
   930  	respErrors, err := parseResponseErrors(body)
   931  	if err != nil {
   932  		return nil, err // parseResponseErrors always return verror.ServerError
   933  	}
   934  	respError := fmt.Sprintf("unexpected status code on Venafi Cloud registration. Status: %s\n", httpStatus)
   935  	for _, e := range respErrors {
   936  		respError += fmt.Sprintf("Error Code: %d Error: %s\n", e.Code, e.Message)
   937  	}
   938  	return nil, fmt.Errorf("%w: %v", verror.ServerError, respError)
   939  }
   940  
   941  func parseCitDetailsData(b []byte, status int) (*certificateTemplate, error) {
   942  
   943  	var cits CertificateTemplates
   944  	var cit certificateTemplate
   945  
   946  	if status == http.StatusOK { //update case
   947  		err := json.Unmarshal(b, &cit)
   948  
   949  		if err != nil {
   950  			return nil, err
   951  		}
   952  	} else { //create case
   953  		err := json.Unmarshal(b, &cits)
   954  
   955  		if err != nil {
   956  			return nil, err
   957  		}
   958  
   959  		//we just get the cit we created/updated
   960  		cit = cits.CertificateTemplates[0]
   961  	}
   962  
   963  	return &cit, nil
   964  }
   965  
   966  func isWildCard(cnRegex []string) bool {
   967  	if len(cnRegex) > 0 {
   968  		for _, val := range cnRegex {
   969  			if !(strings.HasPrefix(val, "[*a")) {
   970  				return false
   971  			}
   972  		}
   973  		return true
   974  	}
   975  	return false
   976  }
   977  
   978  func getServiceAccountTokenURL(rawURL string) (string, error) {
   979  	// removing trailing slash from util.NormalizeURL function
   980  	_, err := url.ParseRequestURI(rawURL)
   981  	if err != nil {
   982  		return "", fmt.Errorf("token url error: %w", err)
   983  	}
   984  
   985  	return rawURL, nil
   986  }