github.com/Venafi/vcert/v5@v5.10.2/pkg/endpoint/endpoint.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 endpoint 18 19 import ( 20 "crypto/ecdsa" 21 "crypto/ed25519" 22 "crypto/rsa" 23 "crypto/x509" 24 "encoding/pem" 25 "errors" 26 "fmt" 27 "net" 28 "net/http" 29 "regexp" 30 "time" 31 32 "github.com/Venafi/vcert/v5/pkg/certificate" 33 "github.com/Venafi/vcert/v5/pkg/domain" 34 "github.com/Venafi/vcert/v5/pkg/policy" 35 ) 36 37 const SDKName = "Venafi VCert-Go" 38 39 var LocalIP string 40 41 // ConnectorType represents the available connectors 42 type ConnectorType int 43 44 const ( 45 ConnectorTypeUndefined ConnectorType = iota 46 // ConnectorTypeFake is a fake connector for tests 47 ConnectorTypeFake 48 // ConnectorTypeCloud represents the Cloud connector type 49 ConnectorTypeCloud 50 // ConnectorTypeTPP represents the TPP connector type 51 ConnectorTypeTPP 52 // ConnectorTypeFirefly represents the Firefly connector type 53 ConnectorTypeFirefly 54 ) 55 56 func init() { 57 LocalIP = getPrimaryNetAddr() 58 } 59 60 func (t ConnectorType) String() string { 61 switch t { 62 case ConnectorTypeUndefined: 63 return "Undefined Endpoint" 64 case ConnectorTypeFake: 65 return "Fake Endpoint" 66 case ConnectorTypeCloud: 67 return "Venafi as a Service" 68 case ConnectorTypeTPP: 69 return "Trust Protection Platform" 70 case ConnectorTypeFirefly: 71 return "Firefly" 72 default: 73 return fmt.Sprintf("unexpected connector type: %d", t) 74 } 75 } 76 77 // Connector provides a common interface for external communications with TPP or Venafi Cloud 78 type Connector interface { 79 // GetType returns a connector type (cloud/TPP/fake). Can be useful because some features are not supported by a Cloud connection. 80 GetType() ConnectorType 81 // SetZone sets a zone (by name) for requests with this connector. 82 SetZone(z string) 83 // SetHTTPClient allows to set custom http.Client to this Connector. 84 SetHTTPClient(client *http.Client) 85 Ping() (err error) 86 // Authenticate is usually called by NewClient and it is not required that you manually call it. 87 Authenticate(auth *Authentication) (err error) 88 89 // ReadPolicyConfiguration returns information about zone policies. It can be used for checking request compatibility with policies. 90 ReadPolicyConfiguration() (policy *Policy, err error) 91 // ReadZoneConfiguration returns the zone configuration. A zone configuration includes zone policy and additional zone information. 92 ReadZoneConfiguration() (config *ZoneConfiguration, err error) 93 // GetZonesByParent returns a list of valid zones specified by parent 94 GetZonesByParent(parent string) ([]string, error) 95 // GenerateRequest update certificate.Request with data from zone configuration. 96 GenerateRequest(config *ZoneConfiguration, req *certificate.Request) (err error) 97 98 // ResetCertificate resets the state of a certificate. 99 // This function is idempotent, i.e., it won't fail if there is nothing to be reset. 100 ResetCertificate(req *certificate.Request, restart bool) (err error) 101 // RequestCertificate makes a request to the server with data for enrolling the certificate. 102 RequestCertificate(req *certificate.Request) (requestID string, err error) 103 // RetrieveCertificate immediately returns an enrolled certificate. Otherwise, RetrieveCertificate waits and retries during req.Timeout. 104 RetrieveCertificate(req *certificate.Request) (certificates *certificate.PEMCollection, err error) 105 ProvisionCertificate(req *domain.ProvisioningRequest, options *domain.ProvisioningOptions) (*domain.ProvisioningMetadata, error) 106 IsCSRServiceGenerated(req *certificate.Request) (bool, error) 107 RevokeCertificate(req *certificate.RevocationRequest) error 108 RenewCertificate(req *certificate.RenewalRequest) (requestID string, err error) 109 RetireCertificate(req *certificate.RetireRequest) error 110 // ImportCertificate adds an existing certificate to Venafi Platform even if the certificate was not issued by Venafi Cloud or Venafi Platform. For information purposes. 111 ImportCertificate(req *certificate.ImportRequest) (*certificate.ImportResponse, error) 112 // ListCertificates returns a list of certificates from inventory that matches the filter 113 ListCertificates(filter Filter) ([]certificate.CertificateInfo, error) 114 SearchCertificates(req *certificate.SearchRequest) (*certificate.CertSearchResponse, error) 115 // SearchCertificate returns a valid certificate 116 // 117 // If it returns no error, the certificate returned should be the latest [1] 118 // exact matching zone [2], CN and sans.DNS [3] provided, with a minimum 119 // validity of `certMinTimeLeft` 120 // 121 // [1] the one with the longest validity; field named ValidTo for TPP and 122 // validityEnd for VaaS 123 // [2] application name for VaaS 124 // [3] an array of strings representing the DNS names 125 SearchCertificate(zone string, cn string, sans *certificate.Sans, certMinTimeLeft time.Duration) (*certificate.CertificateInfo, error) 126 RetrieveCertificateMetaData(dn string) (*certificate.CertificateMetaData, error) 127 128 SetPolicy(name string, ps *policy.PolicySpecification) (string, error) 129 GetPolicy(name string) (*policy.PolicySpecification, error) 130 131 RequestSSHCertificate(req *certificate.SshCertRequest) (response *certificate.SshCertificateObject, err error) 132 RetrieveSSHCertificate(req *certificate.SshCertRequest) (response *certificate.SshCertificateObject, err error) 133 RetrieveSshConfig(ca *certificate.SshCaTemplateRequest) (*certificate.SshConfig, error) 134 RetrieveAvailableSSHTemplates() ([]certificate.SshAvaliableTemplate, error) 135 136 // SynchronousRequestCertificate makes a request to the server with data for enrolling the certificate and returns the enrolled certificate. 137 SynchronousRequestCertificate(req *certificate.Request) (certificates *certificate.PEMCollection, err error) 138 // SupportSynchronousRequestCertificate returns if the connector support synchronous calls to request a certificate. 139 SupportSynchronousRequestCertificate() bool 140 141 RetrieveSystemVersion() (string, error) 142 WriteLog(req *LogRequest) error 143 // SetUserAgent sets the value of the UserAgent header in HTTP requests to 144 // Venafi API endpoints by this connector. 145 // The default is `vcert/v5`. 146 // Further reading: https://www.rfc-editor.org/rfc/rfc9110#field.user-agent 147 SetUserAgent(userAgent string) 148 } 149 150 type Filter struct { 151 Limit *int 152 WithExpired bool 153 } 154 155 // todo: replace with verror 156 // ErrRetrieveCertificateTimeout provides a common error structure for a timeout while retrieving a certificate 157 type ErrRetrieveCertificateTimeout struct { 158 CertificateID string 159 } 160 161 func (err ErrRetrieveCertificateTimeout) Error() string { 162 return fmt.Sprintf("Operation timed out. You may try retrieving the certificate later using Pickup ID: %s", err.CertificateID) 163 } 164 165 // todo: replace with verror 166 // ErrCertificatePending provides a common error structure for a timeout while retrieving a certificate 167 type ErrCertificatePending struct { 168 CertificateID string 169 Status string 170 } 171 172 func (err ErrCertificatePending) Error() string { 173 if err.Status == "" { 174 return fmt.Sprintf("Issuance is pending. You may try retrieving the certificate later using Pickup ID: %s", err.CertificateID) 175 } 176 return fmt.Sprintf("Issuance is pending. You may try retrieving the certificate later using Pickup ID: %s\n\tStatus: %s", err.CertificateID, err.Status) 177 } 178 179 type ErrCertificateRejected struct { 180 CertificateID string 181 Status string 182 } 183 184 func (err ErrCertificateRejected) Error() string { 185 if err.Status == "" { 186 return fmt.Sprintf("Certificate request was rejected. You may need to verify the certificate id: %s", err.CertificateID) 187 } 188 return fmt.Sprintf("Status: %s", err.Status) 189 } 190 191 // Policy is struct that contains restrictions for certificates. Most of the fields contains list of regular expression. 192 // For satisfying policies, all values in the certificate field must match AT LEAST ONE regular expression in corresponding policy field. 193 type Policy struct { 194 SubjectCNRegexes []string 195 SubjectORegexes []string 196 SubjectOURegexes []string 197 SubjectSTRegexes []string 198 SubjectLRegexes []string 199 SubjectCRegexes []string 200 // AllowedKeyConfigurations lists all allowed key configurations. Certificate key configuration have to be listed in this list. 201 // For example: If key has type RSA and length 2048 bit for satisfying the policy, that list must contain AT LEAST ONE configuration with type RSA and value 2048 in KeySizes list of this configuration. 202 AllowedKeyConfigurations []AllowedKeyConfiguration 203 // DnsSanRegExs is a list of regular expressions that show allowable DNS names in SANs. 204 DnsSanRegExs []string 205 // IpSanRegExs is a list of regular expressions that show allowable DNS names in SANs. 206 IpSanRegExs []string 207 EmailSanRegExs []string 208 UriSanRegExs []string 209 UpnSanRegExs []string 210 AllowWildcards bool 211 AllowKeyReuse bool 212 } 213 214 // ZoneConfiguration provides a common structure for certificate request data provided by the remote endpoint 215 type ZoneConfiguration struct { 216 Organization string 217 OrganizationalUnit []string 218 Country string 219 Province string 220 Locality string 221 Policy 222 HashAlgorithm x509.SignatureAlgorithm 223 CustomAttributeValues map[string]string 224 KeyConfiguration *AllowedKeyConfiguration 225 } 226 227 type LogRequest struct { 228 LogID string `json:"ID,omitempty"` 229 Component string `json:",omitempty"` 230 Text1 string `json:",omitempty"` 231 Text2 string `json:",omitempty"` 232 Value1 string `json:",omitempty"` 233 Value2 string `json:",omitempty"` 234 SourceIp string `json:",omitempty"` 235 Severity string `json:",omitempty"` 236 } 237 238 // AllowedKeyConfiguration contains an allowed key type with its sizes or curves 239 type AllowedKeyConfiguration struct { 240 KeyType certificate.KeyType 241 KeySizes []int 242 KeyCurves []certificate.EllipticCurve 243 } 244 245 // NewZoneConfiguration creates a new zone configuration which creates the map used in the configuration 246 func NewZoneConfiguration() *ZoneConfiguration { 247 zc := ZoneConfiguration{} 248 zc.CustomAttributeValues = make(map[string]string) 249 250 return &zc 251 } 252 253 // ValidateCertificateRequest validates the request against the Policy 254 func (p *Policy) ValidateCertificateRequest(request *certificate.Request) error { 255 256 const ( 257 emailError = "email addresses %v do not match regular expressions: %v" 258 ipError = "IP addresses %v do not match regular expressions: %v" 259 uriError = "URIs %v do not match regular expressions: %v" 260 organizationError = "organization %v doesn't match regular expressions: %v" 261 organizationUnitError = "organization unit %v doesn't match regular expressions: %v" 262 countryError = "country %v doesn't match regular expressions: %v" 263 locationError = "location %v doesn't match regular expressions: %v" 264 provinceError = "state (province) %v doesn't match regular expressions: %v" 265 keyError = "the requested Key Type and Size do not match any of the allowed Key Types and Sizes" 266 ) 267 err := p.SimpleValidateCertificateRequest(*request) 268 if err != nil { 269 return err 270 } 271 csr := request.GetCSR() 272 if len(csr) > 0 { 273 pemBlock, _ := pem.Decode(csr) 274 parsedCSR, err := x509.ParseCertificateRequest(pemBlock.Bytes) 275 if err != nil { 276 return err 277 } 278 if !isComponentValid(parsedCSR.EmailAddresses, p.EmailSanRegExs, true) { 279 return fmt.Errorf(emailError, p.EmailSanRegExs, p.EmailSanRegExs) 280 } 281 ips := make([]string, len(parsedCSR.IPAddresses)) 282 for i, ip := range parsedCSR.IPAddresses { 283 ips[i] = ip.String() 284 } 285 if !isComponentValid(ips, p.IpSanRegExs, true) { 286 return fmt.Errorf(ipError, p.IpSanRegExs, p.IpSanRegExs) 287 } 288 uris := make([]string, len(parsedCSR.URIs)) 289 for i, uri := range parsedCSR.URIs { 290 uris[i] = uri.String() 291 } 292 if !isComponentValid(uris, p.UriSanRegExs, true) { 293 return fmt.Errorf(uriError, uris, p.UriSanRegExs) 294 } 295 if !isComponentValid(parsedCSR.Subject.Organization, p.SubjectORegexes, false) { 296 return fmt.Errorf(organizationError, p.SubjectORegexes, p.SubjectORegexes) 297 } 298 299 if !isComponentValid(parsedCSR.Subject.OrganizationalUnit, p.SubjectOURegexes, false) { 300 return fmt.Errorf(organizationUnitError, parsedCSR.Subject.OrganizationalUnit, p.SubjectOURegexes) 301 } 302 303 if !isComponentValid(parsedCSR.Subject.Country, p.SubjectCRegexes, false) { 304 return fmt.Errorf(countryError, parsedCSR.Subject.Country, p.SubjectCRegexes) 305 } 306 307 if !isComponentValid(parsedCSR.Subject.Locality, p.SubjectLRegexes, false) { 308 return fmt.Errorf(locationError, parsedCSR.Subject.Locality, p.SubjectLRegexes) 309 } 310 311 if !isComponentValid(parsedCSR.Subject.Province, p.SubjectSTRegexes, false) { 312 return fmt.Errorf(provinceError, parsedCSR.Subject.Province, p.SubjectSTRegexes) 313 } 314 if len(p.AllowedKeyConfigurations) > 0 { 315 var keyValid bool 316 if parsedCSR.PublicKeyAlgorithm == x509.RSA { 317 pubkey, ok := parsedCSR.PublicKey.(*rsa.PublicKey) 318 if ok { 319 keyValid = checkKey(certificate.KeyTypeRSA, pubkey.Size()*8, "", p.AllowedKeyConfigurations) 320 } else { 321 return fmt.Errorf("invalid key in csr") 322 } 323 } else if parsedCSR.PublicKeyAlgorithm == x509.ECDSA { 324 pubkey, ok := parsedCSR.PublicKey.(*ecdsa.PublicKey) 325 if ok { 326 keyValid = checkKey(certificate.KeyTypeECDSA, 0, pubkey.Curve.Params().Name, p.AllowedKeyConfigurations) 327 } else { 328 return fmt.Errorf("invalid key in csr") 329 } 330 } else if parsedCSR.PublicKeyAlgorithm == x509.Ed25519 { 331 _, ok := parsedCSR.PublicKey.(*ed25519.PublicKey) 332 if ok { 333 keyValid = checkKey(certificate.KeyTypeECDSA, 0, "ed25519", p.AllowedKeyConfigurations) 334 } else { 335 return fmt.Errorf("invalid key in csr") 336 } 337 } 338 if !keyValid { 339 return errors.New(keyError) 340 } 341 } 342 343 } else { 344 //todo: add ip, email, uri cheking 345 if !isComponentValid(request.Subject.Organization, p.SubjectORegexes, false) { 346 return fmt.Errorf(organizationError, request.Subject.Organization, p.SubjectORegexes) 347 } 348 if !isComponentValid(request.Subject.OrganizationalUnit, p.SubjectOURegexes, false) { 349 return fmt.Errorf(organizationUnitError, request.Subject.OrganizationalUnit, p.SubjectOURegexes) 350 } 351 if !isComponentValid(request.Subject.Province, p.SubjectSTRegexes, false) { 352 return fmt.Errorf(provinceError, request.Subject.Province, p.SubjectSTRegexes) 353 } 354 if !isComponentValid(request.Subject.Locality, p.SubjectLRegexes, false) { 355 return fmt.Errorf(locationError, request.Subject.Locality, p.SubjectLRegexes) 356 } 357 if !isComponentValid(request.Subject.Country, p.SubjectCRegexes, false) { 358 return fmt.Errorf(countryError, request.Subject.Country, p.SubjectCRegexes) 359 } 360 361 if len(p.AllowedKeyConfigurations) > 0 { 362 if !checkKey(request.KeyType, request.KeyLength, request.KeyCurve.String(), p.AllowedKeyConfigurations) { 363 return errors.New(keyError) 364 } 365 } 366 } 367 368 return nil 369 } 370 371 // SimpleValidateCertificateRequest functions just check Common Name and SANs mathching with policies 372 func (p *Policy) SimpleValidateCertificateRequest(request certificate.Request) error { 373 csr := request.GetCSR() 374 const ( 375 cnError = "common name %s is not allowed in this policy: %v" 376 SANsError = "DNS SANs %v do not match regular expressions: %v" 377 ) 378 if len(csr) > 0 { 379 pemBlock, _ := pem.Decode(csr) 380 parsedCSR, err := x509.ParseCertificateRequest(pemBlock.Bytes) 381 if err != nil { 382 return err 383 } 384 if !checkStringByRegexp(parsedCSR.Subject.CommonName, p.SubjectCNRegexes) { 385 return fmt.Errorf(cnError, parsedCSR.Subject.CommonName, p.SubjectCNRegexes) 386 } 387 if !isComponentValid(parsedCSR.DNSNames, p.DnsSanRegExs, true) { 388 return fmt.Errorf(SANsError, parsedCSR.DNSNames, p.DnsSanRegExs) 389 } 390 } else { 391 if !checkStringByRegexp(request.Subject.CommonName, p.SubjectCNRegexes) { 392 return fmt.Errorf(cnError, request.Subject.CommonName, p.SubjectCNRegexes) 393 } 394 if !isComponentValid(request.DNSNames, p.DnsSanRegExs, true) { 395 return fmt.Errorf(SANsError, request.DNSNames, p.DnsSanRegExs) 396 } 397 } 398 return nil 399 } 400 401 func checkKey(kt certificate.KeyType, bitsize int, curveStr string, allowed []AllowedKeyConfiguration) (valid bool) { 402 for _, allowedKey := range allowed { 403 if allowedKey.KeyType == kt { 404 switch allowedKey.KeyType { 405 case certificate.KeyTypeRSA: 406 return intInSlice(bitsize, allowedKey.KeySizes) 407 case certificate.KeyTypeECDSA: 408 var curve certificate.EllipticCurve 409 if err := curve.Set(curveStr); err != nil { 410 return false 411 } 412 return curveInSlice(curve, allowedKey.KeyCurves) 413 case certificate.KeyTypeED25519: 414 // ED25519 Key is fixed by its own on size. 415 // Currently, as VaaS sees ED25519 as another curve, we do two things: 416 // 1. If from flow of: 417 // -> cfg = ReadZoneConfiguration() 418 // -> cfg.ValidateCertificateRequest(enrollRequest) 419 // -> cfg.UpdateCertificateRequest(enrollReq) 420 // we allow the user on setting the EllipticCurve or to leave it empty 421 auxCurve := certificate.EllipticCurveED25519 422 if curveStr == "" || curveStr == auxCurve.String() { 423 return true 424 } 425 default: 426 return 427 } 428 } else if kt == certificate.KeyTypeED25519 && allowedKey.KeyType == certificate.KeyTypeECDSA { 429 // 2. else we validate as policy returns to us ED25199 as an elliptic curve from ECDSA from VaaS 430 // flow - You already have a configuration, you read from it and you validate the policy against it: 431 // -> policy = cfg.ReadPolicyConfiguration() 432 // -> err = policy.ValidateCertificateRequest(enrollRequest) 433 var curve certificate.EllipticCurve 434 if err := curve.Set("ed25519"); err != nil { 435 return false 436 } 437 return curveInSlice(curve, allowedKey.KeyCurves) 438 } 439 } 440 return 441 } 442 443 func intInSlice(i int, s []int) bool { 444 for _, j := range s { 445 if i == j { 446 return true 447 } 448 } 449 return false 450 } 451 452 func curveInSlice(i certificate.EllipticCurve, s []certificate.EllipticCurve) bool { 453 for _, j := range s { 454 if i == j { 455 return true 456 } 457 } 458 return false 459 } 460 461 func checkStringByRegexp(s string, regexs []string) bool { 462 for _, r := range regexs { 463 matched, err := regexp.MatchString(r, s) 464 if err == nil && matched { 465 return true 466 } 467 } 468 return false 469 } 470 471 func isComponentValid(ss []string, regexs []string, optional bool) bool { 472 if optional && len(ss) == 0 { 473 return true 474 } 475 if len(ss) == 0 { 476 ss = []string{""} 477 } 478 for _, s := range ss { 479 if !checkStringByRegexp(s, regexs) { 480 return false 481 } 482 } 483 return true 484 } 485 486 // UpdateCertificateRequest updates a certificate request based on the zone configuration retrieved from the remote endpoint 487 func (z *ZoneConfiguration) UpdateCertificateRequest(request *certificate.Request) { 488 if len(request.Subject.Organization) == 0 && z.Organization != "" { 489 request.Subject.Organization = []string{z.Organization} 490 } 491 492 if len(request.Subject.OrganizationalUnit) == 0 && z.OrganizationalUnit != nil { 493 request.Subject.OrganizationalUnit = z.OrganizationalUnit 494 } 495 496 if len(request.Subject.Country) == 0 && z.Country != "" { 497 request.Subject.Country = []string{z.Country} 498 } 499 500 if len(request.Subject.Province) == 0 && z.Province != "" { 501 request.Subject.Province = []string{z.Province} 502 } 503 504 if len(request.Subject.Locality) == 0 && z.Locality != "" { 505 request.Subject.Locality = []string{z.Locality} 506 } 507 508 if z.HashAlgorithm != x509.UnknownSignatureAlgorithm { 509 request.SignatureAlgorithm = z.HashAlgorithm 510 } else { 511 request.SignatureAlgorithm = x509.SHA256WithRSA 512 } 513 514 if z.KeyConfiguration != nil { 515 if request.KeyType.String() == "" { 516 request.KeyType = z.KeyConfiguration.KeyType 517 } 518 if request.KeyType == certificate.KeyTypeRSA { 519 if len(z.KeyConfiguration.KeySizes) != 0 && request.KeyLength == 0 { 520 request.KeyLength = z.KeyConfiguration.KeySizes[0] 521 } 522 } 523 if request.KeyType == certificate.KeyTypeECDSA { 524 if len(z.KeyConfiguration.KeyCurves) != 0 && request.KeyCurve == certificate.EllipticCurveNotSet { 525 request.KeyCurve = z.KeyConfiguration.KeyCurves[0] 526 } 527 } 528 } else { 529 // Zone config has no key length parameters, so we just pass user's -key-size or fall to default 2048 530 if request.KeyType == certificate.KeyTypeRSA && request.KeyLength == 0 { 531 request.KeyLength = 2048 532 } 533 } 534 } 535 536 func getPrimaryNetAddr() string { 537 conn, err := net.Dial("udp", "8.8.8.8:80") 538 if err != nil { 539 return "0.0.0.0" 540 } 541 defer conn.Close() 542 return conn.LocalAddr().(*net.UDPAddr).IP.String() 543 }