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

     1  /*
     2   * Copyright 2018-2023 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 certificate
    18  
    19  import (
    20  	"crypto"
    21  	"crypto/ecdsa"
    22  	"crypto/ed25519"
    23  	"crypto/elliptic"
    24  	"crypto/rand"
    25  	"crypto/rsa"
    26  	"crypto/x509"
    27  	"encoding/pem"
    28  	"fmt"
    29  	"reflect"
    30  	"sort"
    31  	"time"
    32  
    33  	"github.com/youmark/pkcs8"
    34  
    35  	"github.com/Venafi/vcert/v5/pkg/util"
    36  	"github.com/Venafi/vcert/v5/pkg/verror"
    37  )
    38  
    39  const (
    40  	DefaultRSAlength int = 2048
    41  )
    42  
    43  func AllSupportedKeySizes() []int {
    44  	return []int{1024, DefaultRSAlength, 3072, 4096, 8192}
    45  }
    46  
    47  //SSH Certificate structures
    48  
    49  // SshCertRequest This request is a standard one, it will hold data for tpp request
    50  // and in the future it will hold VaS data.
    51  type SshCertRequest struct {
    52  	Template             string
    53  	PolicyDN             string
    54  	ObjectName           string
    55  	DestinationAddresses []string
    56  	KeyId                string
    57  	Principals           []string
    58  	ValidityPeriod       string
    59  	PublicKeyData        string
    60  	Extensions           []string
    61  	ForceCommand         string
    62  	SourceAddresses      []string
    63  
    64  	PickupID                  string
    65  	Guid                      string
    66  	IncludePrivateKeyData     bool
    67  	PrivateKeyPassphrase      string
    68  	PrivateKeyFormat          string
    69  	IncludeCertificateDetails bool
    70  
    71  	Timeout time.Duration
    72  }
    73  
    74  type TPPSshCertRequest struct {
    75  	CADN                      string                 `json:"CADN,omitempty"`
    76  	PolicyDN                  string                 `json:"PolicyDN,omitempty"`
    77  	ObjectName                string                 `json:"ObjectName,omitempty"`
    78  	DestinationAddresses      []string               `json:"DestinationAddresses,omitempty"`
    79  	KeyId                     string                 `json:"KeyId,omitempty"`
    80  	Principals                []string               `json:"Principals,omitempty"`
    81  	ValidityPeriod            string                 `json:"ValidityPeriod,omitempty"`
    82  	PublicKeyData             string                 `json:"PublicKeyData,omitempty"`
    83  	Extensions                map[string]interface{} `json:"Extensions,omitempty"`
    84  	ForceCommand              string                 `json:"ForceCommand,omitempty"`
    85  	SourceAddresses           []string               `json:"SourceAddresses,omitempty"`
    86  	IncludePrivateKeyData     bool                   `json:"IncludePrivateKeyData,omitempty"`
    87  	PrivateKeyPassphrase      string                 `json:"PrivateKeyPassphrase,omitempty"`
    88  	IncludeCertificateDetails bool                   `json:"IncludeCertificateDetails,omitempty"`
    89  	ProcessingTimeout         string                 `json:"ProcessingTimeout,omitempty"`
    90  }
    91  
    92  type TppSshCertResponseInfo struct {
    93  	ErrorCode    int
    94  	ErrorMessage string
    95  	Success      bool
    96  }
    97  
    98  type TppSshCertRetrieveRequest struct {
    99  	Guid                      string
   100  	DN                        string
   101  	IncludePrivateKeyData     bool
   102  	PrivateKeyPassphrase      string
   103  	PrivateKeyFormat          string
   104  	IncludeCertificateDetails bool
   105  }
   106  
   107  type TppSshCertOperationResponse struct {
   108  	ProcessingDetails  ProcessingDetails
   109  	Guid               string
   110  	DN                 string
   111  	CertificateData    string
   112  	PrivateKeyData     string
   113  	PublicKeyData      string
   114  	CAGuid             string
   115  	CADN               string
   116  	CertificateDetails SshCertificateDetails
   117  	Response           TppSshCertResponseInfo
   118  }
   119  
   120  type SshCertificateObject struct {
   121  	Guid               string
   122  	DN                 string
   123  	CAGuid             string
   124  	CADN               string
   125  	CertificateData    string
   126  	PrivateKeyData     string
   127  	PublicKeyData      string
   128  	CertificateDetails SshCertificateDetails
   129  	ProcessingDetails  ProcessingDetails
   130  }
   131  
   132  type SshCertificateDetails struct {
   133  	KeyType                      string                 `json:"KeyType,omitempty"`
   134  	CertificateType              string                 `json:"CertificateType,omitempty"`
   135  	CertificateFingerprintSHA256 string                 `json:"CertificateFingerprintSHA256,omitempty"`
   136  	CAFingerprintSHA256          string                 `json:"CAFingerprintSHA256,omitempty"`
   137  	KeyID                        string                 `json:"KeyID,omitempty"`
   138  	SerialNumber                 string                 `json:"SerialNumber,omitempty"`
   139  	Principals                   []string               `json:"Principals,omitempty"`
   140  	ValidFrom                    int64                  `json:"ValidFrom,omitempty"`
   141  	ValidTo                      int64                  `json:"ValidTo,omitempty"`
   142  	ForceCommand                 string                 `json:"ForceCommand,omitempty"`
   143  	SourceAddresses              []string               `json:"SourceAddresses,omitempty"`
   144  	PublicKeyFingerprintSHA256   string                 `json:"PublicKeyFingerprintSHA256,omitempty"`
   145  	Extensions                   map[string]interface{} `json:"Extensions,omitempty"`
   146  }
   147  
   148  type ProcessingDetails struct {
   149  	Status            string `json:"Status,omitempty"`
   150  	StatusDescription string `json:"StatusDescription,omitempty"`
   151  }
   152  
   153  type RevocationRequest struct {
   154  	CertificateDN string
   155  	Thumbprint    string
   156  	Reason        string
   157  	Comments      string
   158  	Disable       bool
   159  }
   160  
   161  type RetireRequest struct {
   162  	CertificateDN string
   163  	Thumbprint    string
   164  	Description   string
   165  }
   166  
   167  type RenewalRequest struct {
   168  	CertificateDN      string // these fields are for certificate lookup on remote
   169  	Thumbprint         string
   170  	CertificateRequest *Request // here CSR should be filled
   171  }
   172  
   173  type ImportRequest struct {
   174  	PolicyDN        string
   175  	ObjectName      string
   176  	CertificateData string
   177  	PrivateKeyData  string
   178  	Password        string
   179  	Reconcile       bool
   180  	CustomFields    []CustomField
   181  }
   182  
   183  type ImportResponse struct {
   184  	CertificateDN      string `json:",omitempty"`
   185  	CertId             string `json:",omitempty"`
   186  	CertificateVaultId int    `json:",omitempty"`
   187  	Guid               string `json:",omitempty"`
   188  	PrivateKeyVaultId  int    `json:",omitempty"`
   189  }
   190  
   191  type Sans struct {
   192  	DNS   []string
   193  	Email []string `json:",omitempty"`
   194  	IP    []string `json:",omitempty"`
   195  	URI   []string `json:",omitempty"`
   196  	UPN   []string `json:",omitempty"`
   197  }
   198  
   199  type CertificateInfo struct {
   200  	ID         string `json:",omitempty"`
   201  	CN         string
   202  	SANS       Sans
   203  	Serial     string
   204  	Thumbprint string
   205  	ValidFrom  time.Time
   206  	ValidTo    time.Time
   207  }
   208  
   209  type SearchRequest []string
   210  
   211  type CertSearchResponse struct {
   212  	Certificates []CertSeachInfo `json:"Certificates"`
   213  	Count        int             `json:"TotalCount"`
   214  }
   215  
   216  type CertificateMetaData struct {
   217  	Approver               []string `json:"Approver"`
   218  	CreatedOn              string   `json:"CreatedOn"`
   219  	CertificateAuthorityDN string   `json:"CertificateAuthorityDN"`
   220  	Contact                []string `json:"Contact"`
   221  	CreatedBy              []string `json:"CreatedBy"`
   222  	CertificateDetails     struct {
   223  		AIACAIssuerURL        []string  `json:"AIACAIssuerURL"`
   224  		AIAKeyIdentifier      string    `json:"AIAKeyIdentifier"`
   225  		C                     string    `json:"C"`
   226  		CDPURI                string    `json:"CDPURI"`
   227  		CN                    string    `json:"CN"`
   228  		EnhancedKeyUsage      string    `json:"EnhancedKeyUsage"`
   229  		Issuer                string    `json:"Issuer"`
   230  		KeyAlgorithm          string    `json:"KeyAlgorithm"`
   231  		KeySize               int       `json:"KeySize"`
   232  		KeyUsage              string    `json:"KeyUsage"`
   233  		L                     string    `json:"L"`
   234  		O                     string    `json:"O"`
   235  		OU                    []string  `json:"OU"`
   236  		PublicKeyHash         string    `json:"PublicKeyHash"`
   237  		S                     string    `json:"S"`
   238  		SKIKeyIdentifier      string    `json:"SKIKeyIdentifier"`
   239  		Serial                string    `json:"Serial"`
   240  		SignatureAlgorithm    string    `json:"SignatureAlgorithm"`
   241  		SignatureAlgorithmOID string    `json:"SignatureAlgorithmOID"`
   242  		StoreAdded            time.Time `json:"StoreAdded"`
   243  		Subject               string    `json:"Subject"`
   244  		TemplateMajorVersion  string    `json:"TemplateMajorVersion"`
   245  		TemplateMinorVersion  string    `json:"TemplateMinorVersion"`
   246  		TemplateName          string    `json:"TemplateName"`
   247  		TemplateOID           string    `json:"TemplateOID"`
   248  		Thumbprint            string    `json:"Thumbprint"`
   249  		ValidFrom             time.Time `json:"ValidFrom"`
   250  		ValidTo               time.Time `json:"ValidTo"`
   251  	} `json:"CertificateDetails"`
   252  
   253  	RenewalDetails struct {
   254  		City               string   `json:"City"`
   255  		Country            string   `json:"Country"`
   256  		KeySize            int      `json:"KeySize"`
   257  		Organization       string   `json:"Organization"`
   258  		OrganizationalUnit []string `json:"OrganizationalUnit"`
   259  		State              string   `json:"State"`
   260  		Subject            string   `json:"Subject"`
   261  	} `json:"RenewalDetails"`
   262  
   263  	ValidationDetails struct {
   264  		LastValidationStateUpdate time.Time `json:"LastValidationStateUpdate"`
   265  		NetworkValidationDisabled bool      `json:"NetworkValidationDisabled"`
   266  		ValidationDisabled        bool      `json:"ValidationDisabled"`
   267  	} `json:"ValidationDetails"`
   268  
   269  	CustomFields []CustomFieldDetails `json:"CustomFields"`
   270  
   271  	DN             string `json:"DN"`
   272  	Guid           string `json:"Guid"`
   273  	ManagementType string `json:"ManagementType"`
   274  	Name           string `json:"Name"`
   275  	Origin         string `json:"Origin"`
   276  	ParentDn       string `json:"ParentDn"`
   277  	SchemaClass    string `json:"SchemaClass"`
   278  }
   279  type CustomFieldDetails struct {
   280  	Name  string   `json:"Name"`
   281  	Type  string   `json:"Type"`
   282  	Value []string `json:"Value"`
   283  }
   284  
   285  type CertSeachInfo struct {
   286  	CertificateRequestId   string `json:"DN"`
   287  	CertificateRequestGuid string `json:"Guid"`
   288  }
   289  
   290  // Deprecated: GenerateRequest is deprecated
   291  // Please use method Request.GenerateCSR()
   292  // GenerateRequest generates a certificate request
   293  // TODO: Remove usage from all libraries, deprecated
   294  func GenerateRequest(request *Request, privateKey crypto.Signer) error {
   295  	pk := request.PrivateKey
   296  	request.PrivateKey = privateKey
   297  	err := request.GenerateCSR()
   298  	request.PrivateKey = pk
   299  	return err
   300  }
   301  
   302  func publicKey(priv crypto.Signer) crypto.PublicKey {
   303  	if priv != nil {
   304  		return priv.Public()
   305  	}
   306  	return nil
   307  }
   308  
   309  func PublicKey(priv crypto.Signer) crypto.PublicKey {
   310  	return publicKey(priv)
   311  }
   312  
   313  // GetPrivateKeyPEMBock gets the private key as a PEM data block
   314  func GetPrivateKeyPEMBock(key crypto.Signer, format ...string) (*pem.Block, error) {
   315  	currentFormat := ""
   316  	if len(format) > 0 && format[0] != "" {
   317  		currentFormat = format[0]
   318  	}
   319  	switch k := key.(type) {
   320  	case *rsa.PrivateKey:
   321  		if currentFormat == "legacy-pem" {
   322  			return &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(k)}, nil
   323  		} else {
   324  			dataBytes, err := pkcs8.MarshalPrivateKey(key.(*rsa.PrivateKey), nil, nil)
   325  			if err != nil {
   326  				return nil, err
   327  			}
   328  			return &pem.Block{Type: "PRIVATE KEY", Bytes: dataBytes}, err
   329  		}
   330  	case *ecdsa.PrivateKey:
   331  		if currentFormat == "legacy-pem" {
   332  			b, err := x509.MarshalECPrivateKey(k)
   333  			if err != nil {
   334  				return nil, err
   335  			}
   336  			return &pem.Block{Type: "EC PRIVATE KEY", Bytes: b}, nil
   337  		} else {
   338  			dataBytes, err := pkcs8.MarshalPrivateKey(key.(*ecdsa.PrivateKey), nil, nil)
   339  			if err != nil {
   340  				return nil, err
   341  			}
   342  			return &pem.Block{Type: "PRIVATE KEY", Bytes: dataBytes}, err
   343  		}
   344  	case ed25519.PrivateKey:
   345  		if currentFormat == "legacy-pem" {
   346  			return nil, fmt.Errorf("%w: unable to format Key. Legacy format for ed25519 is not supported", verror.VcertError)
   347  		} else {
   348  			dataBytes, err := pkcs8.MarshalPrivateKey(key.(ed25519.PrivateKey), nil, nil)
   349  			if err != nil {
   350  				return nil, err
   351  			}
   352  			return &pem.Block{Type: "PRIVATE KEY", Bytes: dataBytes}, err
   353  		}
   354  	default:
   355  		return nil, fmt.Errorf("%w: unable to format Key", verror.VcertError)
   356  	}
   357  }
   358  
   359  // GetEncryptedPrivateKeyPEMBock gets the private key as an encrypted PEM data block
   360  func GetEncryptedPrivateKeyPEMBock(key crypto.Signer, password []byte, format ...string) (*pem.Block, error) {
   361  	currentFormat := ""
   362  	if len(format) > 0 && format[0] != "" {
   363  		currentFormat = format[0]
   364  	}
   365  	switch k := key.(type) {
   366  	case *rsa.PrivateKey:
   367  		if currentFormat == "legacy-pem" {
   368  			return util.X509EncryptPEMBlock(rand.Reader, "RSA PRIVATE KEY", x509.MarshalPKCS1PrivateKey(k), password, util.PEMCipherAES256)
   369  		} else {
   370  			dataBytes, err := pkcs8.MarshalPrivateKey(key.(*rsa.PrivateKey), password, nil)
   371  			if err != nil {
   372  				return nil, err
   373  			}
   374  			return &pem.Block{Type: "ENCRYPTED PRIVATE KEY", Bytes: dataBytes}, err
   375  		}
   376  	case *ecdsa.PrivateKey:
   377  		if currentFormat == "legacy-pem" {
   378  			b, err := x509.MarshalECPrivateKey(k)
   379  			if err != nil {
   380  				return nil, err
   381  			}
   382  			return util.X509EncryptPEMBlock(rand.Reader, "EC PRIVATE KEY", b, password, util.PEMCipherAES256)
   383  		} else {
   384  			dataBytes, err := pkcs8.MarshalPrivateKey(key.(*ecdsa.PrivateKey), password, nil)
   385  			if err != nil {
   386  				return nil, err
   387  			}
   388  			return &pem.Block{Type: "ENCRYPTED PRIVATE KEY", Bytes: dataBytes}, err
   389  		}
   390  	case ed25519.PrivateKey:
   391  		if currentFormat == "legacy-pem" {
   392  			return nil, fmt.Errorf("%w: unable to format Key. Legacy format for ed25519 is not supported", verror.VcertError)
   393  		} else {
   394  			dataBytes, err := pkcs8.MarshalPrivateKey(key.(ed25519.PrivateKey), password, nil)
   395  			if err != nil {
   396  				return nil, err
   397  			}
   398  			return &pem.Block{Type: "ENCRYPTED PRIVATE KEY", Bytes: dataBytes}, err
   399  		}
   400  	default:
   401  		return nil, fmt.Errorf("%w: unable to format Key", verror.VcertError)
   402  	}
   403  }
   404  
   405  // GetCertificatePEMBlock gets the certificate as a PEM data block
   406  func GetCertificatePEMBlock(cert []byte) *pem.Block {
   407  	return &pem.Block{Type: "CERTIFICATE", Bytes: cert}
   408  }
   409  
   410  // GetCertificateRequestPEMBlock gets the certificate request as a PEM data block
   411  func GetCertificateRequestPEMBlock(request []byte) *pem.Block {
   412  	return &pem.Block{Type: "CERTIFICATE REQUEST", Bytes: request}
   413  }
   414  
   415  // GenerateECDSAPrivateKey generates a new ecdsa private key using the curve specified
   416  func GenerateECDSAPrivateKey(curve EllipticCurve) (crypto.Signer, error) {
   417  	var priv crypto.Signer
   418  	var c elliptic.Curve
   419  	var err error
   420  	if curve == EllipticCurveNotSet {
   421  		curve = EllipticCurveDefault
   422  	}
   423  
   424  	switch curve {
   425  	case EllipticCurveP521:
   426  		c = elliptic.P521()
   427  	case EllipticCurveP384:
   428  		c = elliptic.P384()
   429  	case EllipticCurveP256:
   430  		c = elliptic.P256()
   431  	case EllipticCurveED25519:
   432  		return nil, fmt.Errorf("%w: unable to generate ECDSA key. ED25519 curve is not supported, use GenerateED25519PrivateKey instead", verror.VcertError)
   433  	}
   434  
   435  	priv, err = ecdsa.GenerateKey(c, rand.Reader)
   436  	if err != nil {
   437  		return nil, err
   438  	}
   439  
   440  	return priv, nil
   441  }
   442  
   443  func GenerateED25519PrivateKey() (crypto.Signer, error) {
   444  	_, priv, err := ed25519.GenerateKey(rand.Reader)
   445  	if err != nil {
   446  		return nil, err
   447  	}
   448  	return priv, nil
   449  }
   450  
   451  // GenerateRSAPrivateKey generates a new rsa private key using the size specified
   452  func GenerateRSAPrivateKey(size int) (*rsa.PrivateKey, error) {
   453  	priv, err := rsa.GenerateKey(rand.Reader, size)
   454  	if err != nil {
   455  		return nil, err
   456  	}
   457  
   458  	return priv, nil
   459  }
   460  
   461  // NewRequest duplicates new Request object based on issued certificate
   462  func NewRequest(cert *x509.Certificate) *Request {
   463  	req := &Request{}
   464  
   465  	// First populate with *cert content
   466  	req.Subject = cert.Subject
   467  	req.DNSNames = cert.DNSNames
   468  	req.EmailAddresses = cert.EmailAddresses
   469  	req.IPAddresses = cert.IPAddresses
   470  	req.URIs = cert.URIs
   471  	req.UPNs, _ = getUserPrincipalNameSANs(cert)
   472  
   473  	req.SignatureAlgorithm = cert.SignatureAlgorithm
   474  	switch pub := cert.PublicKey.(type) {
   475  	case *rsa.PublicKey:
   476  		req.KeyType = KeyTypeRSA
   477  		req.KeyLength = pub.N.BitLen()
   478  	case *ecdsa.PublicKey:
   479  		req.KeyType = KeyTypeECDSA
   480  		_ = req.KeyCurve.Set(pub.Curve.Params().Name)
   481  	case ed25519.PublicKey:
   482  		req.KeyType = KeyTypeED25519
   483  		_ = req.KeyCurve.Set("ed25519")
   484  	default:
   485  		// vcert only works with RSA, ECDSA & Ed25519 keys
   486  	}
   487  	return req
   488  }
   489  
   490  // FindNewestCertificateWithSans finds a certificate from a list of certificates whose Sans.DNS matches and is
   491  // the newest
   492  func FindNewestCertificateWithSans(certificates []*CertificateInfo, sans_ *Sans) (*CertificateInfo, error) {
   493  	sans := Sans{}
   494  
   495  	if sans_ != nil {
   496  		sans.DNS = sans_.DNS
   497  	}
   498  
   499  	// order provided SANS-DNS
   500  	sort.Strings(sans.DNS)
   501  
   502  	// create local variable to hold the newest certificate
   503  	var newestCertificate *CertificateInfo
   504  	for _, certificate := range certificates {
   505  		// order certificate SANS before comparison
   506  		if certificate.SANS.DNS != nil {
   507  			sort.Strings(certificate.SANS.DNS)
   508  		}
   509  		// exact match SANs
   510  		if reflect.DeepEqual(sans.DNS, certificate.SANS.DNS) {
   511  			// update the certificate to the newest match
   512  			if newestCertificate == nil || certificate.ValidTo.Unix() > newestCertificate.ValidTo.Unix() {
   513  				newestCertificate = certificate
   514  			}
   515  		}
   516  	}
   517  
   518  	// a valid certificate has been found, return it
   519  	if newestCertificate != nil {
   520  		return newestCertificate, nil
   521  	}
   522  
   523  	// fail, since no valid certificate was found at this point
   524  	return nil, verror.NoCertificateFoundError
   525  }