-
Notifications
You must be signed in to change notification settings - Fork 1.6k
/
coinbase_waas.go
149 lines (122 loc) · 4.25 KB
/
coinbase_waas.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
package coinbase_waas
import (
"context"
"crypto/ecdsa"
"crypto/x509"
"encoding/pem"
"errors"
"net/http"
"strings"
regexp "github.com/wasilibs/go-re2"
"github.com/coinbase/waas-client-library-go/auth"
"github.com/coinbase/waas-client-library-go/clients"
v1clients "github.com/coinbase/waas-client-library-go/clients/v1"
v1 "github.com/coinbase/waas-client-library-go/gen/go/coinbase/cloud/pools/v1"
"github.com/google/uuid"
"google.golang.org/api/googleapi"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
)
type Scanner struct {
client *http.Client
}
// Ensure the Scanner satisfies the interface at compile time.
var _ detectors.Detector = (*Scanner)(nil)
var (
// Reference: https://docs.cloud.coinbase.com/waas/docs/auth
keyNamePat = regexp.MustCompile(`(organizations\\*/\w{8}-\w{4}-\w{4}-\w{4}-\w{12}\\*/apiKeys\\*/\w{8}-\w{4}-\w{4}-\w{4}-\w{12})`)
privKeyPat = regexp.MustCompile(`(-----BEGIN EC(?:DSA)? PRIVATE KEY-----(?:\r|\n|\\+r|\\+n)(?:[a-zA-Z0-9+/]+={0,2}(?:\r|\n|\\+r|\\+n))+-----END EC(?:DSA)? PRIVATE KEY-----(?:\r|\n|\\+r|\\+n)?)`)
nameReplacer = strings.NewReplacer("\\", "")
keyReplacer = strings.NewReplacer(
"\r\n", "\n",
"\\r\\n", "\n",
"\\n", "\n",
"\\r", "\n",
)
)
// 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{"organizations", "apiKeys", "begin ec"}
}
const maxPrivateKeySize = 4096
// MaxSecretSize returns the maximum size of a secret that this detector can find.
func (s Scanner) MaxSecretSize() int64 { return maxPrivateKeySize }
// FromData will find and optionally verify CoinbaseWaaS 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)
keyNameMatches := keyNamePat.FindAllStringSubmatch(dataStr, -1)
privKeyMatches := privKeyPat.FindAllStringSubmatch(dataStr, -1)
for _, keyNameMatch := range keyNameMatches {
resKeyNameMatch := nameReplacer.Replace(strings.TrimSpace(keyNameMatch[1]))
for _, privKeyMatch := range privKeyMatches {
resPrivKeyMatch := keyReplacer.Replace(strings.TrimSpace(privKeyMatch[1]))
if !isValidECPrivateKey([]byte(resPrivKeyMatch)) {
continue
}
s1 := detectors.Result{
DetectorType: detectorspb.DetectorType_CoinbaseWaaS,
Raw: []byte(resPrivKeyMatch),
RawV2: []byte(resKeyNameMatch + ":" + resPrivKeyMatch),
}
if verify {
isVerified, verificationErr := s.verifyMatch(ctx, resKeyNameMatch, resPrivKeyMatch)
s1.Verified = isVerified
s1.SetVerificationError(verificationErr, resPrivKeyMatch)
}
results = append(results, s1)
// If we've found a verified match with this ID, we don't need to look for anymore. So move on to the next ID.
if s1.Verified {
break
}
}
}
return results, nil
}
func isValidECPrivateKey(pemKey []byte) bool {
block, _ := pem.Decode(pemKey)
if block == nil {
return false
}
key, err := x509.ParseECPrivateKey(block.Bytes)
if err != nil {
return false
}
// Check the key type
if _, ok := key.Public().(*ecdsa.PublicKey); !ok {
return false
}
return true
}
func (s Scanner) verifyMatch(ctx context.Context, apiKeyName, privKey string) (bool, error) {
authOpt := clients.WithAPIKey(&auth.APIKey{
Name: apiKeyName,
PrivateKey: privKey,
})
clientOpt := clients.WithHTTPClient(s.client)
client, err := v1clients.NewPoolServiceClient(ctx, authOpt, clientOpt)
if err != nil {
return false, err
}
// Lookup an arbitrary pool name that shouldn't exist.
_, err = client.GetPool(ctx, &v1.GetPoolRequest{Name: uuid.New().String()})
if err != nil {
var apiErr *googleapi.Error
if errors.As(err, &apiErr) {
if apiErr.Code == 401 {
// Invalid |Name| or |PrivateKey|
return false, nil
} else if apiErr.Code == 404 {
// Valid |Name| and |PrivateKey| but the pool doesn't exist (expected).
return true, nil
}
}
// Unhandled error.
return false, err
}
// In theory this will never happen, but it also indicates a valid key.
return true, nil
}
func (s Scanner) Type() detectorspb.DetectorType {
return detectorspb.DetectorType_CoinbaseWaaS
}