Skip to content

Commit

Permalink
added functionality for scanning subjects
Browse files Browse the repository at this point in the history
Signed-off-by: linus-sun <[email protected]>
  • Loading branch information
linus-sun committed Oct 30, 2024
1 parent b3b5cb9 commit d80b1c0
Show file tree
Hide file tree
Showing 12 changed files with 281 additions and 74 deletions.
File renamed without changes.
33 changes: 0 additions & 33 deletions pkg/ct/verifier_test.go → pkg/ct/consistency_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,7 @@ package ct

import (
"encoding/base64"
"fmt"
"net/http"
"net/http/httptest"
"os"
"testing"

Expand All @@ -30,37 +28,6 @@ import (
ctclient "github.com/google/certificate-transparency-go/client"
)

const (
ValidSTHResponseTreeSize = 3721782
ValidSTHResponseTimestamp uint64 = 1396609800587
ValidSTHResponseSHA256RootHash = "SxKOxksguvHPyUaKYKXoZHzXl91Q257+JQ0AUMlFfeo="
ValidSTHResponseTreeHeadSignature = "BAMARjBEAiBUYO2tODlUUw4oWGiVPUHqZadRRyXs9T2rSXchA79VsQIgLASkQv3cu4XdPFCZbgFkIUefniNPCpO3LzzHX53l+wg="
GetSTHConsistencyEmptyResp = `{ "consistency": [ ] }`
)

// serveHandlerAt returns a test HTTP server that only expects requests at the given path, and invokes
// the provided handler for that path.
func serveHandlerAt(t *testing.T, path string, handler func(http.ResponseWriter, *http.Request)) *httptest.Server {
t.Helper()
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == path {
handler(w, r)
} else {
t.Fatalf("Incorrect URL path: %s", r.URL.Path)
}
}))
}

// serveRspAt returns a test HTTP server that returns a canned response body rsp for a given path.
func serveRspAt(t *testing.T, path, rsp string) *httptest.Server {
t.Helper()
return serveHandlerAt(t, path, func(w http.ResponseWriter, _ *http.Request) {
if _, err := fmt.Fprint(w, rsp); err != nil {
t.Fatal(err)
}
})
}

// Test VerifyCertificateTransparencyConsistency
func TestVerifyCertificateTransparencyConsistency(t *testing.T) {
// TODO: placeholder test, fill this out with mock CT Log client
Expand Down
66 changes: 66 additions & 0 deletions pkg/ct/monitor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Copyright 2024 The Sigstore Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package ct

import (
"context"
"fmt"
"regexp"

ct "github.com/google/certificate-transparency-go"
ctclient "github.com/google/certificate-transparency-go/client"
google_x509 "github.com/google/certificate-transparency-go/x509"
"github.com/sigstore/rekor-monitor/pkg/identity"
)

func GetCTLogEntries(logClient *ctclient.LogClient, startIndex int, endIndex int) ([]ct.LogEntry, error) {
entries, err := logClient.GetEntries(context.Background(), int64(startIndex), int64(endIndex))
if err != nil {
return nil, fmt.Errorf("error retrieving certificate transparency log entries: %v", err)
}
return entries, nil
}

func GetSubjectsFromCertificate(cert *google_x509.Certificate) []string {
subjects := []string{}
for _, atv := range cert.Subject.Names {
value, ok := atv.Value.(string)
if !ok {
continue
}
subjects = append(subjects, value)
}
return subjects
}

func ScanEntrySubject(logEntry ct.LogEntry, monitoredSubjects []string) ([]*identity.LogEntry, error) {
subject := logEntry.X509Cert.Subject.String()
foundEntries := []*identity.LogEntry{}
for _, monitoredSub := range monitoredSubjects {
regex, err := regexp.Compile(monitoredSub)
if err != nil {
return nil, fmt.Errorf("error compiling regex: %v", err)
}
matches := regex.FindAllString(subject, -1)
for _, match := range matches {
foundEntries = append(foundEntries, &identity.LogEntry{
Index: logEntry.Index,
Subject: match,
})
}
}

return foundEntries, nil
}
117 changes: 117 additions & 0 deletions pkg/ct/monitor_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
// Copyright 2024 The Sigstore Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package ct

import (
"fmt"
"net/http"
"reflect"
"regexp"
"testing"

ct "github.com/google/certificate-transparency-go"
ctclient "github.com/google/certificate-transparency-go/client"
"github.com/google/certificate-transparency-go/jsonclient"
"github.com/google/certificate-transparency-go/x509"
"github.com/google/certificate-transparency-go/x509/pkix"
"github.com/sigstore/rekor-monitor/pkg/identity"
)

const (
subjectName = "test-subject"
)

func TestGetCTLogEntries(t *testing.T) {
// TODO: placeholder test- a e2e test of certificate transparency identity monitoring should thoroughly test fetching CT log entries
ts := serverHandlerAt(t, "/ct/v1/get-entries", func(w http.ResponseWriter, r *http.Request) {
q := r.URL.Query()
numRE := regexp.MustCompile("[0-9]+")
if !numRE.MatchString(q["start"][0]) || !numRE.MatchString(q["end"][0]) {
t.Fatalf("Invalid parameter: start=%q, end=%q", q["start"][0], q["end"][0])
}
_, err := fmt.Fprintf(w, `{"entries":[{"leaf_input": "%s","extra_data": "%s"},{"leaf_input": "%s","extra_data": "%s"}]}`,
PrecertEntryB64,
PrecertEntryExtraDataB64,
CertEntryB64,
CertEntryExtraDataB64)
if err != nil {
t.Fatal(err)
}
})
defer ts.Close()
lc, err := ctclient.New(ts.URL, &http.Client{}, jsonclient.Options{})
if err != nil {
t.Fatalf("Failed to create client: %v", err)
}
leaves, err := GetCTLogEntries(lc, 0, 1)
if err != nil {
t.Errorf("GetEntries(0,1)=nil,%v; want 2 leaves,nil", err)
} else if len(leaves) != 2 {
t.Errorf("GetEntries(0,1)=%d leaves,nil; want 2 leaves,nil", len(leaves))
}
}

func TestScanEntrySubject(t *testing.T) {
testCases := map[string]struct {
inputEntry ct.LogEntry
inputSubjects []string
expected []*identity.LogEntry
}{
"no matching subject": {
inputEntry: ct.LogEntry{
Index: 1,
X509Cert: &x509.Certificate{
Subject: pkix.Name{
CommonName: subjectName,
},
},
},
inputSubjects: []string{},
expected: []*identity.LogEntry{},
},
"matching subject": {
inputEntry: ct.LogEntry{
Index: 1,
X509Cert: &x509.Certificate{
Subject: pkix.Name{
CommonName: subjectName,
},
},
},
inputSubjects: []string{subjectName},
expected: []*identity.LogEntry{
{Index: 1,
Subject: subjectName},
},
},
}

for _, tc := range testCases {
logEntries, err := ScanEntrySubject(tc.inputEntry, tc.inputSubjects)
if err != nil {
t.Errorf("received error scanning entry for subjects: %v", err)
}
expected := tc.expected
if logEntries == nil {
if expected != nil {
t.Errorf("received nil, expected log entry")
}
} else {
if !reflect.DeepEqual(logEntries, expected) {
t.Errorf("expected %v, received %v", expected, logEntries)
}
}
}
}
Loading

0 comments on commit d80b1c0

Please sign in to comment.