package gcp import ( "bytes" "context" "crypto/rsa" "crypto/x509" "encoding/json" "encoding/pem" "net/http" "net/url" "strconv" "strings" regexp "github.com/wasilibs/go-re2" "golang.org/x/oauth2/google" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors" "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detector_typepb" ) type Scanner struct{} // Ensure the Scanner satisfies the interface at compile time. var _ interface { detectors.Detector detectors.CustomFalsePositiveChecker detectors.MaxSecretSizeProvider detectors.StartOffsetProvider } = (*Scanner)(nil) var ( keyPat = regexp.MustCompile(`\{[^{]+auth_provider_x509_cert_url[^}]+\}`) ) type gcpKey struct { Type string `json:"type"` ProjectID string `json:"project_id"` PrivateKeyID string `json:"private_key_id"` PrivateKey string `json:"private_key"` ClientEmail string `json:"client_email"` ClientID string `json:"client_id"` AuthURI string `json:"auth_uri"` TokenURI string `json:"token_uri"` AuthProviderX509CertURL string `json:"auth_provider_x509_cert_url"` ClientX509CertURL string `json:"client_x509_cert_url"` } // Keywords are used for efficiently pre-filtering chunks. // Use identifiers in the secret preferably, or the provider name. func (s Scanner) Keywords() []string { return []string{"provider_x509"} } const maxGCPKeySize = 2048 // MaxSecretSize returns the maximum size of a secret that this detector can find. func (Scanner) MaxSecretSize() int64 { return maxGCPKeySize } const startOffset = 4096 // StartOffset returns the start offset for the secret this detector finds. func (Scanner) StartOffset() int64 { return startOffset } func (s Scanner) Type() detector_typepb.DetectorType { return detector_typepb.DetectorType_GCP } func (s Scanner) Description() string { return "GCP (Google Cloud Platform) is a suite of cloud computing services that runs on the same infrastructure that Google uses internally for its end-user products. GCP keys can be used to access and manage these services." } // FromData will find and optionally verify GCP secrets in a given set of bytes. func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) { dataStr := string(data) uniqueMatches := make(map[string]struct{}) for _, match := range keyPat.FindAllString(dataStr, -1) { uniqueMatches[match] = struct{}{} } for match := range uniqueMatches { key := cleanInput(match) creds := gcpKey{} if err := json.NewDecoder(strings.NewReader(key)).Decode(&creds); err != nil { continue } // for Slack mangling (mailto scheme and hyperlinks) if strings.Contains(creds.ClientEmail, ` PEM certificate string. func fetchServiceAccountCerts(ctx context.Context, certsURL string) (map[string]string, error) { req, err := http.NewRequestWithContext(ctx, http.MethodGet, certsURL, nil) if err != nil { return nil, err } req.Header.Set("Accept", "application/json") resp, err := detectors.DetectorHttpClientWithNoLocalAddresses.Do(req) if err != nil { return nil, err } defer func() { _ = resp.Body.Close() }() if resp.StatusCode < 200 || resp.StatusCode >= 300 { return nil, nil } var kidToCert map[string]string if err := json.NewDecoder(resp.Body).Decode(&kidToCert); err != nil { return nil, err } return kidToCert, nil } // parsePrivateKey parses a PEM-encoded private key func parsePrivateKey(privateKeyPEM string) (any, error) { block, _ := pem.Decode([]byte(privateKeyPEM)) if block == nil { return nil, nil } key, err := x509.ParsePKCS8PrivateKey(block.Bytes) if err != nil { // Try PKCS1 if PKCS8 fails key, err = x509.ParsePKCS1PrivateKey(block.Bytes) if err != nil { return nil, err } } return key, nil } // parseCertificate parses a PEM-encoded certificate func parseCertificate(certPEM string) (*x509.Certificate, error) { block, _ := pem.Decode([]byte(certPEM)) if block == nil { return nil, nil } return x509.ParseCertificate(block.Bytes) } func (s Scanner) IsFalsePositive(_ detectors.Result) (bool, string) { return false, "" } // region Helper methods func cleanInput(input string) string { input = strings.ReplaceAll(input, `,\\n`, `\n`) input = strings.ReplaceAll(input, `\"\\n`, `\n`) input = strings.ReplaceAll(input, `\\"`, `"`) // If the JSON is encoded, it needs to be unquoted for `json.Unmarshal` to succeed. // https://github.com/trufflesecurity/trufflehog/issues/2864 if strings.Contains(input, `\"auth_provider_x509_cert_url\"`) { unquoted, err := strconv.Unquote(`"` + input + `"`) if err == nil { return unquoted } } return input } func trimCarets(s string) string { s = strings.TrimPrefix(s, "<") s = strings.TrimSuffix(s, ">") return s } //endregion