diff --git a/README.md b/README.md index 6832cec..7ca3fd4 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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: ``` diff --git a/example/output-sample.parquet b/example/output-sample.parquet deleted file mode 100644 index cf04861..0000000 Binary files a/example/output-sample.parquet and /dev/null differ diff --git a/main.go b/main.go index 0b94758..8c654dc 100644 --- a/main.go +++ b/main.go @@ -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", @@ -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 { @@ -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 } diff --git a/result/result.go b/result/result.go index b9db7b7..3a53773 100644 --- a/result/result.go +++ b/result/result.go @@ -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 { diff --git a/validator/validator.go b/validator/validator.go index 9b223c5..24984cc 100644 --- a/validator/validator.go +++ b/validator/validator.go @@ -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") @@ -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) { @@ -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")