Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions pkg/common/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"crypto/rand"
"io"
"math/big"
mrand "math/rand"
"strings"
)

Expand Down Expand Up @@ -70,3 +71,65 @@ func SliceContainsString(origTargetString string, stringSlice []string, ignoreCa
}
return false, "", 0
}

// GoFakeIt Password generator does not guarantee inclusion of characters.
// Using a custom random password generator with guaranteed inclusions (atleast) of lower, upper, numeric and special characters
func GenerateRandomPassword(lower, upper, numeric, special bool, length int) string {
if length < 1 {
return ""
}

var password []rune
var required []rune
var allowed []rune

lowerChars := []rune("abcdefghijklmnopqrstuvwxyz")
upperChars := []rune("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
specialChars := []rune("!@#$%^&*()-_=+[]{}|;:',.<>?/")
numberChars := []rune("0123456789")

// Ensure inclusion from each requested category
if lower {
rand, _ := rand.Int(rand.Reader, big.NewInt(int64(len(lowerChars))))
ch := lowerChars[rand.Int64()]
required = append(required, ch)
allowed = append(allowed, lowerChars...)
}
if upper {
rand, _ := rand.Int(rand.Reader, big.NewInt(int64(len(upperChars))))
ch := upperChars[rand.Int64()]
required = append(required, ch)
allowed = append(allowed, upperChars...)
}
if numeric {
rand, _ := rand.Int(rand.Reader, big.NewInt(int64(len(numberChars))))
ch := numberChars[rand.Int64()]
required = append(required, ch)
allowed = append(allowed, numberChars...)
}
if special {
rand, _ := rand.Int(rand.Reader, big.NewInt(int64(len(specialChars))))
ch := specialChars[rand.Int64()]
required = append(required, ch)
allowed = append(allowed, specialChars...)
}

if len(allowed) == 0 {
return "" // No character sets enabled
}

// Fill the rest of the password
for i := 0; i < length-len(required); i++ {
rand, _ := rand.Int(rand.Reader, big.NewInt(int64(len(allowed))))
ch := allowed[rand.Int64()]
password = append(password, ch)
}

// Combine required and random characters, then shuffle
password = append(password, required...)
mrand.Shuffle(len(password), func(i, j int) {
password[i], password[j] = password[j], password[i]
})

return string(password)
}
83 changes: 83 additions & 0 deletions pkg/common/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"reflect"
"strings"
"testing"
"unicode"
)

func TestAddItem(t *testing.T) {
Expand Down Expand Up @@ -194,3 +195,85 @@ func TestSliceContainsString(t *testing.T) {
}
}
}

func TestGenerateRandomPassword_Length(t *testing.T) {
pass := GenerateRandomPassword(true, true, true, true, 16)
if len(pass) != 16 {
t.Errorf("expected length 16, got %d", len(pass))
}
}

func TestGenerateRandomPassword_Empty(t *testing.T) {
pass := GenerateRandomPassword(false, false, false, false, 10)
if pass != "" {
t.Errorf("expected empty string, got %q", pass)
}
}

func TestGenerateRandomPassword_RequiredSets(t *testing.T) {
tests := []struct {
name string
lower bool
upper bool
numeric bool
special bool
}{
{"lower only", true, false, false, false},
{"upper only", false, true, false, false},
{"numeric only", false, false, true, false},
{"special only", false, false, false, true},
{"all", true, true, true, true},
{"lower+upper", true, true, false, false},
{"lower+numeric", true, false, true, false},
{"upper+special", false, true, false, true},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
pass := GenerateRandomPassword(tc.lower, tc.upper, tc.numeric, tc.special, 12)
if len(pass) != 12 {
t.Errorf("expected length 12, got %d", len(pass))
}
if tc.lower && !contains(pass, unicode.IsLower) {
t.Errorf("expected at least one lowercase letter")
}
if tc.upper && !contains(pass, unicode.IsUpper) {
t.Errorf("expected at least one uppercase letter")
}
if tc.numeric && !contains(pass, unicode.IsDigit) {
t.Errorf("expected at least one digit")
}
if tc.special && !containsSpecial(pass) {
t.Errorf("expected at least one special character")
}
})
}
}

func TestGenerateRandomPassword_ShortLength(t *testing.T) {
pass := GenerateRandomPassword(true, true, true, true, 0)
if pass != "" {
t.Errorf("expected empty string for length 0, got %q", pass)
}
}

func contains(s string, fn func(rune) bool) bool {
for _, r := range s {
if fn(r) {
return true
}
}
return false
}

func containsSpecial(s string) bool {
specials := "!@#$%^&*()-_=+[]{}|;:',.<>?/"
for _, r := range s {
for _, sr := range specials {
if r == sr {
return true
}
}
}
return false
}
8 changes: 4 additions & 4 deletions pkg/detectors/boxoauth/boxoauth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@ import (

"github.com/google/go-cmp/cmp"

"github.com/brianvoe/gofakeit/v7"
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
)

var (
clientId = gofakeit.Password(true, true, true, false, false, 32)
clientSecret = gofakeit.Password(true, true, true, false, false, 32)
invalidClientSecret = gofakeit.Password(true, true, true, true, false, 32)
clientId = common.GenerateRandomPassword(true, true, true, false, 32)
clientSecret = common.GenerateRandomPassword(true, true, true, false, 32)
invalidClientSecret = common.GenerateRandomPassword(true, true, true, true, 32)
)

func TestBoxOauth_Pattern(t *testing.T) {
Expand Down
55 changes: 2 additions & 53 deletions pkg/detectors/snowflake/snowflake_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,66 +3,15 @@ package snowflake
import (
"context"
"fmt"
"math/rand"
"testing"

"github.com/brianvoe/gofakeit/v7"
"github.com/google/go-cmp/cmp"
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
)

// GoFakeIt Password generator does not guarantee inclusion of characters.
// Using a custom Password gennerator with guaranteed inclusions (atleast) of lower, upper and numeric characters
func generatePassword(lower, upper, numeric bool, length int) string {
if length < 1 {
return ""
}

var password []rune
var required []rune
var allowed []rune

lowerChars := []rune("abcdefghijklmnopqrstuvwxyz")
upperChars := []rune("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
numberChars := []rune("0123456789")

// Ensure inclusion from each requested category
if lower {
ch := lowerChars[rand.Intn(len(lowerChars))]
required = append(required, ch)
allowed = append(allowed, lowerChars...)
}
if upper {
ch := upperChars[rand.Intn(len(upperChars))]
required = append(required, ch)
allowed = append(allowed, upperChars...)
}
if numeric {
ch := numberChars[rand.Intn(len(numberChars))]
required = append(required, ch)
allowed = append(allowed, numberChars...)
}

if len(allowed) == 0 {
return "" // No character sets enabled
}

// Fill the rest of the password
for i := 0; i < length-len(required); i++ {
ch := allowed[rand.Intn(len(allowed))]
password = append(password, ch)
}

// Combine required and random characters, then shuffle
password = append(password, required...)
rand.Shuffle(len(password), func(i, j int) {
password[i], password[j] = password[j], password[i]
})

return string(password)
}

func TestSnowflake_Pattern(t *testing.T) {

validAccount := "tuacoip-zt74995"
Expand All @@ -71,7 +20,7 @@ func TestSnowflake_Pattern(t *testing.T) {
validUsername := gofakeit.Username()
invalidUsername := "[email protected]" // special characters not allowed

validPassword := generatePassword(true, true, true, 10)
validPassword := common.GenerateRandomPassword(true, true, true, false, 10)
invalidPassword := "!12" // invalid length

d := Scanner{}
Expand Down
Loading