Skip to content

Commit

Permalink
parallelize validation
Browse files Browse the repository at this point in the history
  • Loading branch information
gustavoluvizotto committed Sep 25, 2023
1 parent 918bb44 commit 1a73228
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 28 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
# cert-validator
X.509 certificate chain validator.

## Requirements
* Go environment
* Internet connection (to download the CCADB root CA certificates).
This can be avoided by **not** specifying the ```--tls-root``` or ```--smime-root``` flags.

## Build
Clone this repository and run the following command in the toplevel directory:
```shell
Expand Down Expand Up @@ -48,6 +53,7 @@ The input in csv format has the following columns:
id,chain
```
Where ```chain``` is a comma-separated list of PEM-encoded X.509 certificates within double quotes.
The ```id``` parameters is an integer that uniquely identifies the chain.

The output is in parquet format and has the following schema:
```
Expand Down
Binary file removed example/output-sample.parquet
Binary file not shown.
38 changes: 24 additions & 14 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ func main() {
"",
"The input file in CSV format")

var output string
flag.StringVar(&output,
"output",
"",
"The output file in Parquet format (provide extension)")

var logFile string
flag.StringVar(&logFile,
"log-file",
Expand Down Expand Up @@ -65,6 +71,11 @@ func main() {
zerolog.SetGlobalLevel(zerolog.WarnLevel)
}

if output == "" {
log.Fatal().Msg("Output file is required")
return
}

if logFile != "" {
fh, err := os.Create(logFile)
if err != nil {
Expand Down Expand Up @@ -97,21 +108,20 @@ func main() {
certChains = input.LoadParquet(inputParquet)
}

validChains := validateChain(certChains, rootStores, rootCAFile)

result.StoreResult(validChains, "example/output-sample.parquet")
validChainChan := validateChain(certChains, rootStores, rootCAFile)
nrChains := len(certChains)
result.ConsumeResultChannel(*validChainChan, nrChains, output)
}

func validateChain(certChains []input.CertChain, rootStores []string, rootCAFile string) []result.ValidationResult {
var validChains []result.ValidationResult
for _, v := range certChains {
log.Debug().Int32("id", v.Id).Msg("Loaded certificate chain")
isValid, err := validator.ValidateChainPem(v.Chain, rootStores, rootCAFile)
var errStr string
if err != nil {
errStr = err.Error()
}
validChains = append(validChains, result.ValidationResult{Id: v.Id, Error: errStr, IsValid: isValid})
func validateChain(certChains []input.CertChain, rootStores []string, rootCAFile string) *chan result.ValidationResult {
rootCAs, err := validator.GetRootCAs(rootStores, rootCAFile)
if err != nil {
log.Fatal().Msg(err.Error())
return nil
}
validChainChan := make(chan result.ValidationResult)
for _, certChain := range certChains {
go validator.ValidateChainPem(certChain, rootCAs, validChainChan)
}
return validChains
return &validChainChan
}
22 changes: 18 additions & 4 deletions result/result.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,26 @@ import (
)

type ValidationResult struct {
Id int32 `parquet:"name=id, type=INT32"`
IsValid bool `parquet:"name=is_valid, type=BOOLEAN"`
Error string `parquet:"name=error, type=BYTE_ARRAY, convertedtype=UTF8"`
Id int32 `parquet:"name=id, type=INT32"`
IsValid bool `parquet:"name=is_valid, type=BOOLEAN"`
ErrorData string `parquet:"name=error, type=BYTE_ARRAY, convertedtype=UTF8"`
}

func StoreResult(result []ValidationResult, fileName string) {
func ConsumeResultChannel(resultChan chan ValidationResult, nrChains int, fileName string) {
if resultChan == nil {
log.Fatal().Msg("Result channel is nil")
return
}

var validChains []ValidationResult
for i := 0; i < nrChains; i++ {
validChains = append(validChains, <-resultChan)
}

storeResult(validChains, fileName)
}

func storeResult(result []ValidationResult, fileName string) {
// write []ValidationResult to parquet file
fw, err := local.NewLocalFileWriter(fileName)
if err != nil {
Expand Down
24 changes: 14 additions & 10 deletions validator/validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,30 @@ import (
"crypto/x509"
"encoding/pem"
"errors"
"github.com/gustavoluvizotto/cert-validator/input"
"github.com/gustavoluvizotto/cert-validator/result"
"github.com/rs/zerolog/log"
"os"
"strconv"
"time"
)

func ValidateChainPem(certChainStr []string, rootStores []string, rootCAFile string) (bool, error) {
rootCAs, err := getRootCAs(rootStores, rootCAFile)
if err != nil {
log.Fatal().Msg(err.Error())
return false, err
}
func ValidateChainPem(certChain input.CertChain, rootCAs *x509.CertPool, resultChan chan result.ValidationResult) {
log.Debug().Int32("id", certChain.Id).Msg("Validating certificate chain")

// Iterate over the certificates in the chain, permuting the leaf certificate and the intermediate certificates,
// and verify the chain against the root CAs
valResult := result.ValidationResult{Id: certChain.Id, IsValid: false}
var leaf *x509.Certificate
var err error
isValid := false
for i, certStr := range certChainStr {
for i, certStr := range certChain.Chain {
leaf, err = getCertificateFromPEM(certStr)
if err != nil {
continue
}
intermediates := x509.NewCertPool()
for j, certStr2 := range certChainStr {
for j, certStr2 := range certChain.Chain {
if i != j {
if !intermediates.AppendCertsFromPEM([]byte(certStr2)) {
err = errors.New("failed to append intermediate certificate")
Expand All @@ -51,7 +51,11 @@ func ValidateChainPem(certChainStr []string, rootStores []string, rootCAFile str
break
}

return isValid, err
valResult.IsValid = isValid
if err != nil {
valResult.ErrorData = err.Error()
}
resultChan <- valResult
}

func getCertificateFromPEM(certStr string) (*x509.Certificate, error) {
Expand All @@ -71,7 +75,7 @@ func getCertificateFromPEM(certStr string) (*x509.Certificate, error) {
return cert, nil
}

func getRootCAs(rootStores []string, rootCAfile string) (*x509.CertPool, error) {
func GetRootCAs(rootStores []string, rootCAfile string) (*x509.CertPool, error) {
rootCAs, err := x509.SystemCertPool()
if err != nil {
return nil, errors.New("failed to fetch system root CA certificates")
Expand Down

0 comments on commit 1a73228

Please sign in to comment.