Skip to content

Commit

Permalink
Add MatchedIndices functionality for certificate transparency (#535)
Browse files Browse the repository at this point in the history
* add functionality for scanning OID matchers in a CT log entry

Signed-off-by: linus-sun <[email protected]>

* refactor extension functions to support x509 and google_x509

Signed-off-by: linus-sun <[email protected]>

* refactor extension functions to support x509 and google_x509

Signed-off-by: linus-sun <[email protected]>

* add matched indices

Signed-off-by: linus-sun <[email protected]>

---------

Signed-off-by: linus-sun <[email protected]>
  • Loading branch information
linus-sun authored Nov 20, 2024
1 parent 4a90a10 commit 2d5580b
Show file tree
Hide file tree
Showing 2 changed files with 176 additions and 12 deletions.
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)
}
}
}

0 comments on commit 2d5580b

Please sign in to comment.