Support additional data for private keys

This commit is contained in:
Dustin Decker
2024-11-21 16:11:40 -08:00
parent e494eaf7b0
commit adfad3241c
2 changed files with 159 additions and 46 deletions
+81 -21
View File
@@ -6,9 +6,11 @@ import (
"errors"
"fmt"
"net/http"
"reflect"
"strings"
"sync"
"time"
"unicode"
regexp "github.com/wasilibs/go-re2"
"golang.org/x/crypto/ssh"
@@ -97,7 +99,7 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
defer wg.Done()
data, err := lookupFingerprint(ctx, fingerprint, s.IncludeExpired)
if err == nil {
if data != nil {
if len(data.CertificateResults) > 0 {
extraData.Add("certificate_urls", strings.Join(data.CertificateURLs, ", "))
}
} else {
@@ -114,7 +116,7 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
verificationErrors.Add(err)
}
if user != nil {
extraData.Add("github_user", *user)
extraData.Add("github_user", "Key can SSH pull/push as user: "+*user)
}
}()
@@ -127,11 +129,12 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
verificationErrors.Add(err)
}
if user != nil {
extraData.Add("gitlab_user", *user)
extraData.Add("gitlab_user", "Key can SSH pull/push as user: "+*user)
}
}()
wg.Wait()
if len(extraData.data) > 0 {
s1.Verified = true
for k, v := range extraData.data {
@@ -161,27 +164,78 @@ func (s Scanner) Description() string {
type result struct {
CertificateURLs []string
GitHubUsername string
driftwoodResult
}
func lookupFingerprint(ctx context.Context, publicKeyFingerprintInHex string, includeExpired bool) (*result, error) {
func (r result) GetExtraData() map[string]string {
data := map[string]string{}
if len(r.CertificateURLs) > 0 {
data["certificate_urls"] = strings.Join(r.CertificateURLs, ",")
}
if len(r.CertificateResults) > 0 {
for _, cert := range r.CertificateResults {
v := reflect.ValueOf(cert)
t := v.Type()
for i := 0; i < v.NumField(); i++ {
field := v.Field(i)
fieldName := t.Field(i).Name
// Convert field name to snake_case
snakeCase := ""
for i, r := range fieldName {
if i > 0 && unicode.IsUpper(r) {
if string(r) == "D" && i > 0 && string(fieldName[i-1]) == "I" {
snakeCase += string(unicode.ToLower(r))
} else {
snakeCase += "_" + string(unicode.ToLower(r))
}
} else {
snakeCase += string(unicode.ToLower(r))
}
}
switch field.Kind() {
case reflect.String:
if str := field.String(); str != "" {
data[snakeCase] = str
}
case reflect.Slice:
if slice := field.Interface(); field.Len() > 0 {
if strSlice, ok := slice.([]string); ok {
data[snakeCase] = strings.Join(strSlice, ",")
}
}
case reflect.Struct:
if !field.IsZero() {
if timeField, ok := field.Interface().(time.Time); ok {
data[snakeCase] = timeField.String()
}
}
}
}
}
}
return data
}
func lookupFingerprint(ctx context.Context, publicKeyFingerprintInHex string, includeExpired bool) (result, error) {
data := result{}
req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://keychecker.trufflesecurity.com/fingerprint/%s", publicKeyFingerprintInHex), nil)
if err != nil {
return nil, err
return data, err
}
res, err := client.Do(req)
if err != nil {
return nil, err
return data, err
}
defer res.Body.Close()
results := DriftwoodResult{}
results := driftwoodResult{}
err = json.NewDecoder(res.Body).Decode(&results)
if err != nil {
return nil, err
return data, err
}
var data *result
data.driftwoodResult = results
seen := map[string]struct{}{}
for _, r := range results.CertificateResults {
@@ -191,9 +245,6 @@ func lookupFingerprint(ctx context.Context, publicKeyFingerprintInHex string, in
if !includeExpired && time.Since(r.ExpirationTimestamp) > 0 {
continue
}
if data == nil {
data = &result{}
}
data.CertificateURLs = append(data.CertificateURLs, fmt.Sprintf("https://crt.sh/?q=%s", r.CertificateFingerprint))
seen[r.CertificateFingerprint] = struct{}{}
}
@@ -201,14 +252,23 @@ func lookupFingerprint(ctx context.Context, publicKeyFingerprintInHex string, in
return data, nil
}
type DriftwoodResult struct {
CertificateResults []struct {
CertificateFingerprint string `json:"CertificateFingerprint"`
ExpirationTimestamp time.Time `json:"ExpirationTimestamp"`
} `json:"CertificateResults"`
GitHubSSHResults []struct {
Username string `json:"Username"`
} `json:"GitHubSSHResults"`
type driftwoodResult struct {
CertificateResults []certificateResult `json:"CertificateResults,omitempty"`
}
type certificateResult struct {
Domains []string `json:",omitempty"`
CertificateFingerprint string
ExpirationTimestamp time.Time
IssuerName string `json:",omitempty"` // CA information
SubjectName string `json:",omitempty"` // Certificate subject
IssuerOrganization []string `json:",omitempty"` // CA organization(s)
SubjectOrganization []string `json:",omitempty"` // Subject organization(s)
KeyUsages []string `json:",omitempty"` // e.g., ["DigitalSignature", "KeyEncipherment"]
ExtendedKeyUsages []string `json:",omitempty"` // e.g., ["ServerAuth", "ClientAuth"]
SubjectKeyID string `json:",omitempty"` // hex encoded
AuthorityKeyID string `json:",omitempty"` // hex encoded
SerialNumber string `json:",omitempty"` // hex encoded
}
type extraData struct {
+78 -25
View File
@@ -6,7 +6,6 @@ package privatekey
import (
"context"
"fmt"
"os"
"reflect"
"testing"
"time"
@@ -84,26 +83,6 @@ func TestPrivatekey_FromChunk(t *testing.T) {
},
wantErr: false,
},
{
name: "found GitHub SSH private key, verified",
s: Scanner{},
args: args{
ctx: context.Background(),
data: []byte(secretGitHub),
verify: true,
},
want: []detectors.Result{
{
DetectorType: detectorspb.DetectorType_PrivateKey,
Verified: true,
Redacted: "-----BEGIN OPENSSH PRIVATE KEY-----\nb3BlbnNzaC1rZXktdjEAAAAABG5v",
ExtraData: map[string]string{
"github_user": "sirdetectsalot",
},
},
},
wantErr: false,
},
{
name: "found encrypted GitHub SSH private key, verified",
s: Scanner{},
@@ -146,9 +125,6 @@ func TestPrivatekey_FromChunk(t *testing.T) {
t.Errorf("PrivatekeyCI.FromData() error = %v, wantErr %v", err, tt.wantErr)
return
}
if os.Getenv("FORCE_PASS_DIFF") == "true" {
return
}
for i := range got {
if len(got[i].Raw) == 0 {
t.Fatal("no raw secret present")
@@ -208,9 +184,86 @@ func Test_lookupFingerprint(t *testing.T) {
t.Errorf("lookupFingerprint() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(gotFingerprints != nil && len(gotFingerprints.CertificateURLs) > 0, tt.wantFingerprints) {
if !reflect.DeepEqual(len(gotFingerprints.CertificateURLs) > 0, tt.wantFingerprints) {
t.Errorf("lookupFingerprint() = %v, want %v", gotFingerprints, tt.wantFingerprints)
}
})
}
}
func TestResult_GetExtraData(t *testing.T) {
tests := []struct {
name string
result result
want map[string]string
}{
{
name: "no certificate URLs or results",
result: result{
CertificateURLs: []string{},
driftwoodResult: driftwoodResult{
CertificateResults: []certificateResult{},
},
},
want: map[string]string{},
},
{
name: "with certificate URLs",
result: result{
CertificateURLs: []string{"https://crt.sh/?q=1e20c40deb44a8539dd3ac3e8c53b72750cb19f9"},
driftwoodResult: driftwoodResult{
CertificateResults: []certificateResult{},
},
},
want: map[string]string{
"certificate_urls": "https://crt.sh/?q=1e20c40deb44a8539dd3ac3e8c53b72750cb19f9",
},
},
{
name: "with certificate results",
result: result{
CertificateURLs: []string{},
driftwoodResult: driftwoodResult{
CertificateResults: []certificateResult{
{
Domains: []string{"example.com"},
CertificateFingerprint: "1e20c40deb44a8539dd3ac3e8c53b72750cb19f9",
ExpirationTimestamp: time.Date(2023, time.December, 31, 23, 59, 59, 0, time.UTC),
IssuerName: "Example CA",
SubjectName: "example.com",
IssuerOrganization: []string{"Example Org"},
SubjectOrganization: []string{"Example Org"},
KeyUsages: []string{"DigitalSignature", "KeyEncipherment"},
ExtendedKeyUsages: []string{"ServerAuth", "ClientAuth"},
SubjectKeyID: "abcdef123456",
AuthorityKeyID: "123456abcdef",
SerialNumber: "123456",
},
},
},
},
want: map[string]string{
"domains": "example.com",
"certificate_fingerprint": "1e20c40deb44a8539dd3ac3e8c53b72750cb19f9",
"expiration_timestamp": "2023-12-31 23:59:59 +0000 UTC",
"issuer_name": "Example CA",
"subject_name": "example.com",
"issuer_organization": "Example Org",
"subject_organization": "Example Org",
"key_usages": "DigitalSignature,KeyEncipherment",
"extended_key_usages": "ServerAuth,ClientAuth",
"subject_key_id": "abcdef123456",
"authority_key_id": "123456abcdef",
"serial_number": "123456",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if diff := pretty.Compare(tt.result.GetExtraData(), tt.want); diff != "" {
t.Errorf("GetExtraData() diff (-got +want):\n%s", diff)
}
})
}
}