github.com/Venafi/vcert/v5@v5.10.2/pkg/certificate/request.go (about)

     1  package certificate
     2  
     3  import (
     4  	"crypto"
     5  	"crypto/ecdsa"
     6  	"crypto/ed25519"
     7  	"crypto/rand"
     8  	"crypto/rsa"
     9  	"crypto/x509"
    10  	"crypto/x509/pkix"
    11  	"encoding/pem"
    12  	"fmt"
    13  	"net"
    14  	"net/url"
    15  	"strings"
    16  	"time"
    17  
    18  	"github.com/Venafi/vcert/v5/pkg/util"
    19  	"github.com/Venafi/vcert/v5/pkg/verror"
    20  )
    21  
    22  // Request contains data needed to generate a certificate request
    23  // CSR is a PEM-encoded Certificate Signing Request
    24  type Request struct {
    25  	CADN           string
    26  	Subject        pkix.Name
    27  	DNSNames       []string
    28  	OmitSANs       bool
    29  	EmailAddresses []string
    30  	IPAddresses    []net.IP
    31  	URIs           []*url.URL
    32  	UPNs           []string
    33  	// Deprecated: Attributes is deprecated from X509.CertificateRequest. See ExtraExtensions
    34  	// instead. Values override any extensions that would otherwise be produced based on the
    35  	// other fields but are overridden by any extensions specified in Attributes.
    36  	Attributes []pkix.AttributeTypeAndValueSET
    37  	// ExtraExtensions may include SAN values and ExtKeyUsage values. If these are
    38  	// specified as part of ExtraExtensions, they will override the other specified values.
    39  	ExtraExtensions    []pkix.Extension
    40  	SignatureAlgorithm x509.SignatureAlgorithm
    41  	FriendlyName       string
    42  	KeyType            KeyType
    43  	KeyLength          int
    44  	KeyCurve           EllipticCurve
    45  	csr                []byte // should be a PEM-encoded CSR
    46  	PrivateKey         crypto.Signer
    47  	CsrOrigin          CSrOriginOption
    48  	PickupID           string
    49  	//Cloud Certificate ID
    50  	CertID          string
    51  	ChainOption     ChainOption
    52  	KeyPassword     string
    53  	FetchPrivateKey bool
    54  	/*	Thumbprint is here because *Request is used in RetrieveCertificate().
    55  		Code should be refactored so that RetrieveCertificate() uses some abstract search object, instead of *Request{PickupID} */
    56  	Thumbprint string
    57  	// Timeout usage:
    58  	// TPP (a.k.a TLSPDC): we use it in order to set WorkToDoTimeout, that overrides TPP default timeout waiting time for the CA to finish
    59  	// if the value is more than the maximum value, TPP will automatically set the maximum value supported (as of the moment of this
    60  	// commit, 120 seconds).
    61  	// Cloud (a.k.a VaaS a.k.a TLSPC) : We use this timeout in our RetrieveCertificate function which handles a retry logic
    62  	// TPP SSH feature: We override the http client default timeout to perform http requests.
    63  	// Firefly: not usage at all
    64  	//
    65  	// Note:
    66  	// In VCert CLI we have hardcoded 180 seconds for retrieve certificate operation. For VaaS it will set retry logic for
    67  	// 180 seconds and TPP will override CA timeout as the hardcoded value
    68  	Timeout          time.Duration
    69  	CustomFields     []CustomField
    70  	Location         *Location
    71  	ValidityDuration *time.Duration
    72  	ValidityPeriod   string //represents the validity of the certificate expressed as an ISO 8601 duration
    73  	IssuerHint       util.IssuerHint
    74  
    75  	// Contacts allows you to configure email addresses to send notifications
    76  	// about the certificate. This field is TPP-specific.
    77  	//
    78  	// Note: the user who receives the notification isn't automatically given
    79  	// access to that certificate. Access is configured at the policy folder
    80  	// level; if the user doesn't permissions on that folder, they will not be
    81  	// able to see the certificate's status in TPP or remediate the problem
    82  	// through the TPP UI.
    83  	//
    84  	// When an email is used by multiple TPP identities, the first identity
    85  	// found is picked arbitrarily.
    86  	//
    87  	// The scope `configuration` is required. Since Contacts works by searching
    88  	// the emails in the same LDAP or AD as the user attached to the token, you
    89  	// must check that you are using a user in that same identity provider.
    90  	// Contacts doesn't work with the local TPP identities. Using Contacts
    91  	// requires adding `mail` to the list of fields searched when performing a
    92  	// user search, which can be configured in the Venafi Configuration Console
    93  	// by RDP'ing into the TPP VM. This configuration cannot be performed
    94  	// directly in the TPP UI.
    95  	Contacts []string
    96  
    97  	// Allow user to specify whether to include
    98  	ExtKeyUsages ExtKeyUsageSlice
    99  
   100  	// Deprecated: use ValidityDuration instead, this field is ignored if ValidityDuration is set
   101  	ValidityHours int
   102  }
   103  
   104  // SetCSR sets CSR from PEM or DER format
   105  func (request *Request) SetCSR(csr []byte) error {
   106  	pemBlock, _ := pem.Decode(csr)
   107  	if pemBlock != nil {
   108  		if strings.HasSuffix(pemBlock.Type, "CERTIFICATE REQUEST") {
   109  			request.csr = csr
   110  			return nil
   111  		}
   112  	}
   113  
   114  	//Determine CSR type and use appropriate function
   115  	parsedCSR, err := x509.ParseCertificateRequest(csr)
   116  	if err != nil {
   117  		return err
   118  	}
   119  	if parsedCSR != nil {
   120  		request.csr = pem.EncodeToMemory(GetCertificateRequestPEMBlock(csr))
   121  		return nil
   122  	}
   123  	return fmt.Errorf("%w: can't determine CSR type for %s", verror.UserDataError, csr)
   124  }
   125  
   126  // GetCSR returns CSR in PEM format
   127  func (request *Request) GetCSR() []byte {
   128  	return request.csr
   129  }
   130  
   131  // GenerateCSR creates CSR for sending to server based on data from Request fields. It rewrites CSR field if it`s already filled.
   132  func (request *Request) GenerateCSR() error {
   133  	certificateRequest := x509.CertificateRequest{}
   134  	certificateRequest.Subject = request.Subject
   135  
   136  	if !request.OmitSANs {
   137  		addSubjectAltNames(&certificateRequest, request.DNSNames, request.EmailAddresses, request.IPAddresses, request.URIs, request.UPNs)
   138  	}
   139  
   140  	if request.ExtKeyUsages != nil {
   141  		err := addExtKeyUsage(&certificateRequest, request.ExtKeyUsages)
   142  		if err != nil {
   143  			return fmt.Errorf("%w: %s %w", verror.VcertError, "failed to add requested EKUs", err)
   144  		}
   145  	}
   146  
   147  	// If ExtraExtensions are included in request, they may override the SANs and ExtKeyUsages
   148  	//  that were included. This is by design, so that developers using the SDK are able to
   149  	//  craft specific and strange CSRs if required by their use-case, and they won't be clobbered
   150  	//  by other settings.
   151  	if request.ExtraExtensions != nil {
   152  		certificateRequest.ExtraExtensions = request.ExtraExtensions
   153  	}
   154  
   155  	certificateRequest.Attributes = request.Attributes
   156  
   157  	csr, err := x509.CreateCertificateRequest(rand.Reader, &certificateRequest, request.PrivateKey)
   158  	if err != nil {
   159  		csr = nil
   160  	}
   161  	err = request.SetCSR(csr)
   162  	//request.CSR = pem.EncodeToMemory(GetCertificateRequestPEMBlock(csr))
   163  	return err
   164  }
   165  
   166  // GeneratePrivateKey creates private key (if it doesn`t already exist) based on request.KeyType, request.KeyLength and request.KeyCurve fileds
   167  func (request *Request) GeneratePrivateKey() error {
   168  	if request.PrivateKey != nil {
   169  		return nil
   170  	}
   171  	var err error
   172  	switch request.KeyType {
   173  	case KeyTypeECDSA:
   174  		request.PrivateKey, err = GenerateECDSAPrivateKey(request.KeyCurve)
   175  	case KeyTypeED25519:
   176  		request.PrivateKey, err = GenerateED25519PrivateKey()
   177  	case KeyTypeRSA:
   178  		if request.KeyLength == 0 {
   179  			request.KeyLength = DefaultRSAlength
   180  		}
   181  		if request.KeyLength < AllSupportedKeySizes()[0] {
   182  			return fmt.Errorf("key Size must be %d or greater. But it is %d", AllSupportedKeySizes()[0], request.KeyLength)
   183  		}
   184  		request.PrivateKey, err = GenerateRSAPrivateKey(request.KeyLength)
   185  	default:
   186  		return fmt.Errorf("%w: unable to generate certificate request, key type %s is not supported", verror.VcertError, request.KeyType.String())
   187  	}
   188  	return err
   189  }
   190  
   191  // CheckCertificate validate that certificate returned by server matches data in request object. It can be used for control server.
   192  func (request *Request) CheckCertificate(certPEM string) error {
   193  	pemBlock, _ := pem.Decode([]byte(certPEM))
   194  	if pemBlock == nil {
   195  		return fmt.Errorf("%w: invalid pem format certificate %s", verror.CertificateCheckError, certPEM)
   196  	}
   197  	if pemBlock.Type != "CERTIFICATE" {
   198  		return fmt.Errorf("%w: invalid pem type %s (expect CERTIFICATE)", verror.CertificateCheckError, pemBlock.Type)
   199  	}
   200  	cert, err := x509.ParseCertificate(pemBlock.Bytes)
   201  	if err != nil {
   202  		return err
   203  	}
   204  	if request.PrivateKey != nil {
   205  		if request.KeyType.X509Type() != cert.PublicKeyAlgorithm {
   206  			return fmt.Errorf("%w: unmatched key type: %s, %s", verror.CertificateCheckError, request.KeyType.X509Type(), cert.PublicKeyAlgorithm)
   207  		}
   208  		switch cert.PublicKeyAlgorithm {
   209  		case x509.RSA:
   210  			certPubKey := cert.PublicKey.(*rsa.PublicKey)
   211  			reqPubkey, ok := request.PrivateKey.Public().(*rsa.PublicKey)
   212  			if !ok {
   213  				return fmt.Errorf("%w: request KeyType not matched with real PrivateKey type", verror.CertificateCheckError)
   214  			}
   215  
   216  			if certPubKey.N.Cmp(reqPubkey.N) != 0 {
   217  				return fmt.Errorf("%w: unmatched key modulus", verror.CertificateCheckError)
   218  			}
   219  		case x509.ECDSA:
   220  			certPubkey := cert.PublicKey.(*ecdsa.PublicKey)
   221  			reqPubkey, ok := request.PrivateKey.Public().(*ecdsa.PublicKey)
   222  			if !ok {
   223  				return fmt.Errorf("%w: request KeyType not matched with real PrivateKey type", verror.CertificateCheckError)
   224  			}
   225  			if certPubkey.X.Cmp(reqPubkey.X) != 0 {
   226  				return fmt.Errorf("%w: unmatched X for elliptic keys", verror.CertificateCheckError)
   227  			}
   228  		case x509.Ed25519:
   229  			certPubkey := cert.PublicKey.(ed25519.PublicKey)
   230  			reqPubkey, ok := request.PrivateKey.Public().(ed25519.PublicKey)
   231  			if !ok {
   232  				return fmt.Errorf("%w: request KeyType not matched with real PrivateKey type", verror.CertificateCheckError)
   233  			}
   234  			if !certPubkey.Equal(reqPubkey) {
   235  				return fmt.Errorf("%w: unmatched elliptic ed25519 keys", verror.CertificateCheckError)
   236  			}
   237  		default:
   238  			return fmt.Errorf("%w: unknown key algorythm %d", verror.CertificateCheckError, cert.PublicKeyAlgorithm)
   239  		}
   240  	} else if len(request.csr) != 0 {
   241  		pemBlock, _ := pem.Decode(request.csr)
   242  		if pemBlock == nil {
   243  			return fmt.Errorf("%w: bad CSR: %s", verror.CertificateCheckError, string(request.csr))
   244  		}
   245  		csr, err := x509.ParseCertificateRequest(pemBlock.Bytes)
   246  		if err != nil {
   247  			return err
   248  		}
   249  		if cert.PublicKeyAlgorithm != csr.PublicKeyAlgorithm {
   250  			return fmt.Errorf("%w: unmatched key type: %s, %s", verror.CertificateCheckError, cert.PublicKeyAlgorithm, csr.PublicKeyAlgorithm)
   251  		}
   252  		switch csr.PublicKeyAlgorithm {
   253  		case x509.RSA:
   254  			certPubKey := cert.PublicKey.(*rsa.PublicKey)
   255  			reqPubKey := csr.PublicKey.(*rsa.PublicKey)
   256  			if certPubKey.N.Cmp(reqPubKey.N) != 0 {
   257  				return fmt.Errorf("%w: unmatched key modulus", verror.CertificateCheckError)
   258  			}
   259  		case x509.ECDSA:
   260  			certPubKey := cert.PublicKey.(*ecdsa.PublicKey)
   261  			reqPubKey := csr.PublicKey.(*ecdsa.PublicKey)
   262  			if certPubKey.X.Cmp(reqPubKey.X) != 0 {
   263  				return fmt.Errorf("%w: unmatched X for elliptic keys", verror.CertificateCheckError)
   264  			}
   265  		}
   266  	}
   267  	return nil
   268  }