forked from DataDog/datadog-agent
/
credit_cards.go
276 lines (266 loc) · 7.55 KB
/
credit_cards.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
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
// Unless explicitly stated otherwise all files in this repository are licensed
// under the Apache License Version 2.0.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2016-present Datadog, Inc.
package obfuscate
import (
"strings"
"github.com/StackVista/stackstate-agent/pkg/trace/pb"
)
// ccObfuscator maintains credit card obfuscation state and processing.
type ccObfuscator struct {
luhn bool
}
func newCreditCardsObfuscator(useLuhn bool) *ccObfuscator {
cco := &ccObfuscator{luhn: useLuhn}
pb.SetMetaHook(cco.MetaHook)
return cco
}
func (cco *ccObfuscator) Disable() { pb.SetMetaHook(nil) }
// MetaHook checks the tag with the given key and val and returns the final
// value to be assigned to this tag.
//
// For example, in this specific use-case, if the val is detected to be a credit
// card number, "?" will be returned.
func (cco *ccObfuscator) MetaHook(k, v string) (newval string) {
switch k {
case "_sample_rate",
"_sampling_priority_v1",
"error",
"error.msg",
"error.type",
"error.stack",
"env",
"graphql.field",
"graphql.query",
"graphql.type",
"graphql.operation.name",
"grpc.code",
"grpc.method",
"grpc.request",
"http.status_code",
"http.method",
"runtime-id",
"out.host",
"out.port",
"sampling.priority",
"span.type",
"span.name",
"service.name",
"service",
"sql.query",
"version":
// these tags are known to not be credit card numbers
return v
}
if strings.HasPrefix(k, "_dd") {
return v
}
if isCardNumber(v, cco.luhn) {
return "?"
}
return v
}
// isSensitve checks if b could be a credit card number by checking the digit count and IIN prefix.
// If validateLuhn is true, the Luhn checksum is also applied to potential candidates.
func isCardNumber(b string, validateLuhn bool) (ok bool) {
//
// Just credit card numbers for now, based on:
// • https://baymard.com/checkout-usability/credit-card-patterns
// • https://www.regular-expressions.info/creditcard.html
//
if len(b) == 0 {
return false
}
if len(b) < 12 {
// fast path: can not be a credit card
return false
}
if b[0] != ' ' && b[0] != '-' && (b[0] < '0' || b[0] > '9') {
// fast path: only valid characters are 0-9, space (" ") and dash("-")
return false
}
prefix := 0 // holds up to b[:6] digits as a numeric value (for example []byte{"523"} becomes int(523)) for checking prefixes
count := 0 // counts digits encountered
foundPrefix := false // reports whether we've detected a valid prefix
recdigit := func(_ byte) {} // callback on each found digit; no-op by default (we only need this for Luhn)
if validateLuhn {
// we need Luhn checksum validation, so we have to take additional action
// and record all digits found
buf := make([]byte, 0, len(b))
recdigit = func(b byte) { buf = append(buf, b) }
defer func() {
if !ok {
// if isCardNumber returned false, it means that b can not be
// a credit card number
return
}
// potentially a credit card number, run the Luhn checksum
ok = luhnValid(buf)
}()
}
loop:
for i := range b {
// We traverse and search b for a valid IIN credit card prefix based
// on the digits found, ignoring spaces and dashes.
// Source: https://www.regular-expressions.info/creditcard.html
switch b[i] {
case ' ', '-':
// ignore space (' ') and dash ('-')
continue loop
}
if b[i] < '0' || b[i] > '9' {
// not a 0 to 9 digit; can not be a credit card number; abort
return false
}
count++
recdigit(b[i])
if !foundPrefix {
// we have not yet found a valid prefix so we convert the digits
// that we have so far into a numeric value:
prefix = prefix*10 + (int(b[i]) - '0')
maybe, yes := validCardPrefix(prefix)
if yes {
// we've found a valid prefix; continue counting
foundPrefix = true
} else if !maybe {
// this is not a valid prefix and we should not continue looking
return false
}
}
if count > 16 {
// too many digits
return false
}
}
if count < 12 {
// too few digits
return false
}
return foundPrefix
}
// luhnValid checks that the number represented in the given string validates the Luhn Checksum algorithm.
// str is expected to contain exclusively digits at all positions.
//
// See:
// • https://en.wikipedia.org/wiki/Luhn_algorithm
// • https://dev.to/shiraazm/goluhn-a-simple-library-for-generating-calculating-and-verifying-luhn-numbers-588j
//
func luhnValid(str []byte) bool {
var (
sum int
alt bool
)
n := len(str)
for i := n - 1; i > -1; i-- {
if str[i] < '0' || str[i] > '9' {
return false // not a number!
}
mod := int(str[i] - 0x30) // convert byte to int
if alt {
mod *= 2
if mod > 9 {
mod = (mod % 10) + 1
}
}
alt = !alt
sum += mod
}
return sum%10 == 0
}
// validCardPrefix validates whether b is a valid card prefix. Maybe returns true if
// the prefix could be an IIN once more digits are revealed and yes reports whether
// b is a fully valid IIN.
//
// If yes is false and maybe is false, there is no reason to continue searching. The
// prefix is invalid.
//
// IMPORTANT: If adding new prefixes to this algorithm, make sure that you update
// the "maybe" clauses above, in the shorter prefixes than the one you are adding.
// This refers to the cases which return true, false.
//
// TODO(x): this whole code could be code generated from a prettier data structure.
// Ultimately, it could even be user-configurable.
func validCardPrefix(n int) (maybe, yes bool) {
// Validates IIN prefix possibilities
// Source: https://www.regular-expressions.info/creditcard.html
if n > 699999 {
// too long for any known prefix; stop looking
return false, false
}
if n < 10 {
switch n {
case 1, 4:
// 1 & 4 are valid IIN
return false, true
case 2, 3, 5, 6:
// 2, 3, 5, 6 could be the start of valid IIN
return true, false
default:
// invalid IIN
return false, false
}
}
if n < 100 {
if (n >= 34 && n <= 39) ||
(n >= 51 && n <= 55) ||
n == 62 ||
n == 65 {
// 34-39, 51-55, 62, 65 are valid IIN
return false, true
}
if n == 30 || n == 63 || n == 64 || n == 35 || n == 50 || n == 60 ||
(n >= 22 && n <= 27) || (n >= 56 && n <= 58) || (n >= 60 && n <= 69) {
// 30, 63, 64, 35, 50, 60, 22-27, 56-58, 60-69 may end up as valid IIN
return true, false
}
}
if n < 1000 {
if (n >= 300 && n <= 305) ||
(n >= 644 && n <= 649) ||
n == 309 ||
n == 636 {
// 300‑305, 309, 636, 644‑649 are valid IIN
return false, true
}
if (n >= 352 && n <= 358) || n == 501 || n == 601 ||
(n >= 222 && n <= 272) || (n >= 500 && n <= 509) ||
(n >= 560 && n <= 589) || (n >= 600 && n <= 699) {
// 352-358, 501, 601, 222-272, 500-509, 560-589, 600-699 may be a 4 or 6 digit IIN prefix
return true, false
}
}
if n < 10000 {
if (n >= 3528 && n <= 3589) ||
n == 5019 ||
n == 6011 {
// 3528‑3589, 5019, 6011 are valid IINs
return false, true
}
if (n >= 2221 && n <= 2720) || (n >= 5000 && n <= 5099) ||
(n >= 5600 && n <= 5899) || (n >= 6000 && n <= 6999) {
// maybe a 6-digit IIN
return true, false
}
}
if n < 100000 {
if (n >= 22210 && n <= 27209) ||
(n >= 50000 && n <= 50999) ||
(n >= 56000 && n <= 58999) ||
(n >= 60000 && n <= 69999) {
// maybe a 6-digit IIN
return true, false
}
}
if n < 1000000 {
if (n >= 222100 && n <= 272099) ||
(n >= 500000 && n <= 509999) ||
(n >= 560000 && n <= 589999) ||
(n >= 600000 && n <= 699999) {
// 222100‑272099, 500000‑509999, 560000‑589999, 600000‑699999 are valid IIN
return false, true
}
}
// unknown IIN
return false, false
}