Skip to content
This repository has been archived by the owner on Jun 9, 2024. It is now read-only.

Commit

Permalink
Implement certificate transparency (#134)
Browse files Browse the repository at this point in the history
Co-authored-by: Oliver Geiselhardt-Herms <ogeiselhardt-herms@cloudflare.com>
  • Loading branch information
takt and Oliver Geiselhardt-Herms committed Mar 31, 2023
1 parent 181aa8e commit 98a5d28
Show file tree
Hide file tree
Showing 2 changed files with 182 additions and 2 deletions.
111 changes: 111 additions & 0 deletions cmd/octorpki/ct.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package main

import (
"bytes"
"encoding/json"
"flag"
"fmt"
"io"
"net/http"
"time"

"github.com/cloudflare/cfrpki/validator/pki"
"github.com/opentracing/opentracing-go"

librpki "github.com/cloudflare/cfrpki/validator/lib"
ct "github.com/google/certificate-transparency-go"
log "github.com/sirupsen/logrus"
)

var (
// Certificate Transparency
CertTransparency = flag.Bool("ct", false, "Enable Certificate Transparency")
CertTransparencyAddr = flag.String("ct.addr", "https://ct.cloudflare.com/logs/cirrus", "Path of CT")
CertTransparencyThreads = flag.Int("ct.threads", 50, "Threads to send to CT")
CertTransparencyTimeout = flag.Int("ct.timeout", 50, "CT timeout in seconds")
)

func SingleSendCertificateTransparency(httpclient *http.Client, path string, msg *ct.AddChainRequest) error {
buf := bytes.NewBuffer([]byte{})
enc := json.NewEncoder(buf)
enc.Encode(msg)

resp, err := httpclient.Post(fmt.Sprintf("%v/ct/v1/add-chain", path), "application/json", buf)
if err == nil {
respStr, _ := io.ReadAll(resp.Body)
log.Debugf("Sent %v certs %v %v %v", len(msg.Chain), path, string(respStr), err)
}

return err
}

func BatchCertificateTransparency(httpclient *http.Client, path string, d chan *ct.AddChainRequest) {
log.Debugf("Starting BatchCertificateTransparency")

for msg := range d {
err := SingleSendCertificateTransparency(httpclient, path, msg)
if err != nil {
log.Error(err)
}
}
}

func (s *OctoRPKI) SendCertificateTransparency(pSpan opentracing.Span, ctData [][]*pki.PKIFile, threads int, timeout int) {
tracer := opentracing.GlobalTracer()
span := tracer.StartSpan(
"ct",
opentracing.ChildOf(pSpan.Context()),
)
defer span.Finish()

log.Infof("Sending Certificate Transparency (threads=%v)", threads)

httpclient := &http.Client{
Timeout: time.Duration(timeout) * time.Second,
}

dataChan := make(chan *ct.AddChainRequest)
defer close(dataChan)

for i := 0; i < threads; i++ {
go BatchCertificateTransparency(httpclient, s.CTPath, dataChan)
}

var iterations int
for _, certs := range ctData {
chain := make([][]byte, 0)

for _, cert := range certs {
var dataBytes []byte
data, err := s.Fetcher.GetFile(cert)
if cert.Type == pki.TYPE_ROA || cert.Type == pki.TYPE_MFT {
cms, err := librpki.DecodeCMS(data.Data)
if err != nil {
log.Error(err)
continue
}
dataBytes = cms.SignedData.Certificates.Bytes
} else {
dataBytes = data.Data
}

if err != nil {
log.Error(err)
continue
}

chain = append(chain, dataBytes)
}

dataChan <- &ct.AddChainRequest{
Chain: chain,
}

iterations++
if len(ctData) > 0 && len(ctData) >= 20 && iterations%(len(ctData)/20) == 0 {
log.Infof("Sent %v/%v (%v percent) certificates chains to CT %v", iterations, len(ctData), iterations*100/len(ctData), s.CTPath)
}
}

log.Infof("Sent %v chains to Certificate Transparency %v", len(ctData), s.CTPath)
}
73 changes: 71 additions & 2 deletions cmd/octorpki/octorpki.go
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,9 @@ type OctoRPKI struct {

Resources *schemas.ResourcesJSON
ResourcesMu sync.RWMutex

DoCT bool
CTPath string
}

func (s *OctoRPKI) getRRDPFetch() map[string]string {
Expand Down Expand Up @@ -1025,7 +1028,7 @@ func (s *OctoRPKI) signROAList(roaList *prefixfile.ROAList, span opentracing.Spa
roaList.Metadata.SignatureDate = signdate
}

func (s *OctoRPKI) mainValidation(pSpan opentracing.Span) {
func (s *OctoRPKI) mainValidation(pSpan opentracing.Span) [][]*pki.PKIFile {
t1 := time.Now()
ia := make([][]SIA, len(s.Tals))
for i := 0; i < len(ia); i++ {
Expand All @@ -1036,6 +1039,8 @@ func (s *OctoRPKI) mainValidation(pSpan opentracing.Span) {
span := s.tracer.StartSpan("validation", opentracing.ChildOf(pSpan.Context()))
defer span.Finish()

ctData := make([][]*pki.PKIFile, 0)

pkiManagers := make([]*pki.SimpleManager, len(s.Tals))
for i, tal := range s.Tals {
tSpan := s.tracer.StartSpan("explore", opentracing.ChildOf(span.Context()))
Expand Down Expand Up @@ -1113,6 +1118,10 @@ func (s *OctoRPKI) mainValidation(pSpan opentracing.Span) {
sm.Close()
tSpan.LogKV("count-valid", count, "count-total", countExplore)
tSpan.Finish()

if s.DoCT {
ctData = append(ctData, s.ct(pkiManagers, i)...)
}
}

s.setInfoAuthorities(ia)
Expand All @@ -1122,6 +1131,46 @@ func (s *OctoRPKI) mainValidation(pSpan opentracing.Span) {
s.stats.ValidationDuration = t2.Sub(t1)
MetricOperationTime.With(prometheus.Labels{"type": "validation"}).Observe(float64(s.stats.ValidationDuration.Seconds()))
MetricLastValidation.Set(float64(s.LastComputed.Unix()))

return ctData
}

func (s *OctoRPKI) ct(pkiManagers []*pki.SimpleManager, i int) [][]*pki.PKIFile {
skiToAki := make(map[string]string)
skiToPath := make(map[string]*pki.PKIFile)
for _, obj := range pkiManagers[i].Validator.ValidObjects {
res := obj.Resource.(*librpki.RPKICertificate)
ski := hex.EncodeToString(res.Certificate.SubjectKeyId)
aki := hex.EncodeToString(res.Certificate.AuthorityKeyId)
skiToAki[ski] = aki
skiToPath[ski] = obj.File
}

pathCT := make([][]*pki.PKIFile, 0)
for ski, aki := range skiToAki {
skiDone := make(map[string]bool)
skiDone[ski] = true

curAki := aki
curPath := skiToPath[ski]
curPathCT := make([]*pki.PKIFile, 1)
curPathCT[0] = curPath

var ok bool
for curAki != "" && !ok {
ok = skiDone[curAki]
skiDone[curAki] = true

curPath = skiToPath[curAki]
if curAki != "" {
curPathCT = append(curPathCT, curPath)
}
curAki = skiToAki[curAki]
}
pathCT = append(pathCT, curPathCT)
}

return pathCT
}

func (s *OctoRPKI) setInfoAuthorities(ia [][]SIA) {
Expand Down Expand Up @@ -1405,6 +1454,10 @@ func main() {
log.Fatalf("Mode %v is not specified. Choose either server or oneoff", *Mode)
}

if *CertTransparencyThreads < 1 {
*CertTransparencyThreads = 1
}

s.validationLoop()
}

Expand All @@ -1425,6 +1478,8 @@ func NewOctoRPKI(tals []*pki.PKIFile, talNames []string) *OctoRPKI {
stats: newOctoRPKIStats(),
InfoAuthorities: make([][]SIA, 0),
tracer: opentracing.GlobalTracer(),
DoCT: *CertTransparency,
CTPath: *CertTransparencyAddr,
}
}

Expand Down Expand Up @@ -1498,7 +1553,7 @@ func (s *OctoRPKI) validationLoop() {

s.mainRsync(span)

s.mainValidation(span)
ctData := s.mainValidation(span)

// Reduce
changed := s.MainReduce()
Expand All @@ -1524,6 +1579,20 @@ func (s *OctoRPKI) validationLoop() {
break
}

// Certificate Transparency
if s.DoCT && (s.Stable.Load() || !*WaitStable) {
t1 := time.Now().UTC()

s.SendCertificateTransparency(span, ctData, *CertTransparencyThreads, *CertTransparencyTimeout)

t2 := time.Now().UTC()
MetricOperationTime.With(
prometheus.Labels{
"type": "ct",
}).
Observe(float64(t2.Sub(t1).Seconds()))
}

if s.Stable.Load() {
MetricLastStableValidation.Set(float64(s.LastComputed.Unix()))
MetricState.Set(float64(1))
Expand Down

0 comments on commit 98a5d28

Please sign in to comment.