Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add MatchedIndices functionality for certificate transparency #535

Merged
merged 4 commits into from
Nov 20, 2024
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
43 changes: 37 additions & 6 deletions pkg/ct/monitor.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,14 @@ func GetCTLogEntries(logClient *ctclient.LogClient, startIndex int, endIndex int
return entries, nil
}

func ScanEntryCertSubject(logEntry ct.LogEntry, monitoredCertIDs []identity.CertificateIdentity) ([]*identity.LogEntry, error) {
matchedEntries := []*identity.LogEntry{}
func ScanEntryCertSubject(logEntry ct.LogEntry, monitoredCertIDs []identity.CertificateIdentity) ([]identity.LogEntry, error) {
matchedEntries := []identity.LogEntry{}
for _, monitoredCertID := range monitoredCertIDs {
match, sub, iss, err := identity.CertMatchesPolicy(logEntry.X509Cert, monitoredCertID.CertSubject, monitoredCertID.Issuers)
if err != nil {
return nil, fmt.Errorf("error with policy matching at index %d: %w", logEntry.Index, err)
} else if match {
matchedEntries = append(matchedEntries, &identity.LogEntry{
matchedEntries = append(matchedEntries, identity.LogEntry{
CertSubject: sub,
Issuer: iss,
Index: logEntry.Index,
Expand All @@ -49,16 +49,16 @@ func ScanEntryCertSubject(logEntry ct.LogEntry, monitoredCertIDs []identity.Cert
return matchedEntries, nil
}

func ScanEntryOIDExtensions(logEntry ct.LogEntry, monitoredOIDMatchers []extensions.OIDExtension) ([]*identity.LogEntry, error) {
matchedEntries := []*identity.LogEntry{}
func ScanEntryOIDExtensions(logEntry ct.LogEntry, monitoredOIDMatchers []extensions.OIDExtension) ([]identity.LogEntry, error) {
matchedEntries := []identity.LogEntry{}
cert := logEntry.X509Cert
for _, monitoredOID := range monitoredOIDMatchers {
match, _, extValue, err := identity.OIDMatchesPolicy(cert, monitoredOID.ObjectIdentifier, monitoredOID.ExtensionValues)
if err != nil {
return nil, fmt.Errorf("error with policy matching at index %d: %w", logEntry.Index, err)
}
if match {
matchedEntries = append(matchedEntries, &identity.LogEntry{
matchedEntries = append(matchedEntries, identity.LogEntry{
Index: logEntry.Index,
OIDExtension: monitoredOID.ObjectIdentifier,
ExtensionValue: extValue,
Expand All @@ -67,3 +67,34 @@ func ScanEntryOIDExtensions(logEntry ct.LogEntry, monitoredOIDMatchers []extensi
}
return matchedEntries, nil
}

func MatchedIndices(logEntries []ct.LogEntry, mvs identity.MonitoredValues) ([]identity.LogEntry, error) {
matchedEntries := []identity.LogEntry{}
for _, entry := range logEntries {
matchedCertSubjectEntries, err := ScanEntryCertSubject(entry, mvs.CertificateIdentities)
if err != nil {
return nil, err
}
matchedEntries = append(matchedEntries, matchedCertSubjectEntries...)

matchedOIDEntries, err := ScanEntryOIDExtensions(entry, mvs.OIDMatchers)
if err != nil {
return nil, err
}
matchedEntries = append(matchedEntries, matchedOIDEntries...)
}

return matchedEntries, nil
}

func IdentitySearch(client *ctclient.LogClient, startIndex int, endIndex int, mvs identity.MonitoredValues) ([]identity.LogEntry, error) {
retrievedEntries, err := GetCTLogEntries(client, startIndex, endIndex)
if err != nil {
return nil, err
}
matchedEntries, err := MatchedIndices(retrievedEntries, mvs)
if err != nil {
return nil, err
}
return matchedEntries, nil
}
145 changes: 139 additions & 6 deletions pkg/ct/monitor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func TestScanEntryCertSubject(t *testing.T) {
testCases := map[string]struct {
inputEntry ct.LogEntry
inputSubjects []identity.CertificateIdentity
expected []*identity.LogEntry
expected []identity.LogEntry
}{
"no matching subject": {
inputEntry: ct.LogEntry{
Expand All @@ -50,7 +50,7 @@ func TestScanEntryCertSubject(t *testing.T) {
},
},
inputSubjects: []identity.CertificateIdentity{},
expected: []*identity.LogEntry{},
expected: []identity.LogEntry{},
},
"matching subject": {
inputEntry: ct.LogEntry{
Expand All @@ -64,6 +64,9 @@ func TestScanEntryCertSubject(t *testing.T) {
Value: []byte(issuerName),
},
},
Issuer: pkix.Name{
CommonName: issuerName,
},
},
},
inputSubjects: []identity.CertificateIdentity{
Expand All @@ -76,7 +79,7 @@ func TestScanEntryCertSubject(t *testing.T) {
Issuers: []string{},
},
},
expected: []*identity.LogEntry{
expected: []identity.LogEntry{
{Index: 1,
CertSubject: subjectName,
Issuer: issuerName},
Expand Down Expand Up @@ -116,7 +119,7 @@ func TestScanEntryOIDExtensions(t *testing.T) {
testCases := map[string]struct {
inputEntry ct.LogEntry
inputOIDExtensions []extensions.OIDExtension
expected []*identity.LogEntry
expected []identity.LogEntry
}{
"no matching subject": {
inputEntry: ct.LogEntry{
Expand All @@ -129,7 +132,7 @@ func TestScanEntryOIDExtensions(t *testing.T) {
ExtensionValues: []string{extValueString},
},
},
expected: []*identity.LogEntry{},
expected: []identity.LogEntry{},
},
"matching subject": {
inputEntry: ct.LogEntry{
Expand All @@ -142,7 +145,7 @@ func TestScanEntryOIDExtensions(t *testing.T) {
ExtensionValues: []string{extValueString},
},
},
expected: []*identity.LogEntry{
expected: []identity.LogEntry{
{
Index: 1,
OIDExtension: matchedAsn1OID,
Expand All @@ -169,3 +172,133 @@ func TestScanEntryOIDExtensions(t *testing.T) {
}
}
}

func TestMatchedIndices(t *testing.T) {
extCert, err := mockCertificateWithExtension(google_asn1.ObjectIdentifier{2, 5, 29, 17}, "test cert value")
if err != nil {
t.Errorf("Expected nil got %v", err)
}
unmatchedAsn1OID := asn1.ObjectIdentifier{2}
matchedAsn1OID := asn1.ObjectIdentifier{2, 5, 29, 17}
extValueString := "test cert value"
extCert.Extensions = append(extCert.Extensions, pkix.Extension{
Id: google_asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 1},
Value: []byte(issuerName),
})
inputEntries := []ct.LogEntry{
{Index: 1,
X509Cert: &x509.Certificate{
DNSNames: []string{subjectName},
EmailAddresses: []string{organizationName},
Extensions: extCert.Extensions,
Issuer: pkix.Name{
CommonName: issuerName,
},
},
},
}
testCases := map[string]struct {
inputEntries []ct.LogEntry
inputMonitoredValues identity.MonitoredValues
expected []identity.LogEntry
}{
"empty case": {
inputEntries: []ct.LogEntry{},
inputMonitoredValues: identity.MonitoredValues{},
expected: []identity.LogEntry{},
},
"no matching entries": {
inputEntries: inputEntries,
inputMonitoredValues: identity.MonitoredValues{
CertificateIdentities: []identity.CertificateIdentity{
{
CertSubject: "non-matched-subject",
},
},
OIDMatchers: []extensions.OIDExtension{
{
ObjectIdentifier: unmatchedAsn1OID,
ExtensionValues: []string{"unmatched extension value"},
},
},
},
expected: []identity.LogEntry{},
},
"matching certificate identity and issuer": {
inputEntries: inputEntries,
inputMonitoredValues: identity.MonitoredValues{
CertificateIdentities: []identity.CertificateIdentity{
{
CertSubject: subjectName,
Issuers: []string{issuerName},
},
},
},
expected: []identity.LogEntry{
{
Index: 1,
CertSubject: subjectName,
Issuer: issuerName,
},
},
},
"matching OID extension": {
inputEntries: inputEntries,
inputMonitoredValues: identity.MonitoredValues{
OIDMatchers: []extensions.OIDExtension{
{
ObjectIdentifier: matchedAsn1OID,
ExtensionValues: []string{extValueString},
},
},
},
expected: []identity.LogEntry{
{
Index: 1,
OIDExtension: matchedAsn1OID,
ExtensionValue: extValueString,
},
},
},
"matching certificate subject and issuer and OID extension": {
inputEntries: inputEntries,
inputMonitoredValues: identity.MonitoredValues{
CertificateIdentities: []identity.CertificateIdentity{
{
CertSubject: subjectName,
Issuers: []string{issuerName},
},
},
OIDMatchers: []extensions.OIDExtension{
{
ObjectIdentifier: matchedAsn1OID,
ExtensionValues: []string{extValueString},
},
},
},
expected: []identity.LogEntry{
{
Index: 1,
CertSubject: subjectName,
Issuer: issuerName,
},
{
Index: 1,
OIDExtension: matchedAsn1OID,
ExtensionValue: extValueString,
},
},
},
}

for _, tc := range testCases {
matchedEntries, err := MatchedIndices(tc.inputEntries, tc.inputMonitoredValues)
if err != nil {
t.Errorf("error matching indices: %v", err)
}
expected := tc.expected
if !reflect.DeepEqual(matchedEntries, expected) {
t.Errorf("received %v, expected %v", matchedEntries, expected)
}
}
}
Loading