Skip to content

Commit

Permalink
validation of x509 cert chain
Browse files Browse the repository at this point in the history
  • Loading branch information
gustavoluvizotto committed Sep 22, 2023
1 parent ade85f3 commit 918bb44
Show file tree
Hide file tree
Showing 8 changed files with 450 additions and 246 deletions.
36 changes: 30 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,33 @@ go build .
```
## Usage
```shell
./cert-validator --input-parquet=example/input-sample.parquet -v 2 --log-file=example/log.json
./cert-validator --input-csv=example/input-sample.csv --tls-root --root-ca-file=/etc/ssl/cert.pem --log-file=example/log.json -v 2
```
The ```-v``` flag controls the verbosity of the output.
It is optional and defaults to 0.
The ```--input-csv``` flag specifies the path to the input csv file.
The ```--tls-root``` flag specifies whether to use the CCADB TLS/SSL root CA certificates.
This flag is optional and defaults to ```false```.
Alternatively, you can specify ```--smime-root``` to use the CCADB S/MIME root CA certificates.
This flag is also optional and defaults to ```false```.
The ```--root-ca-file``` flag specifies the path to a root CA certificates file.
This flag is optional and defaults to empty.
The ```--log-file``` flag specifies the path to the log file.
It is optional and defaults to ```stdout```.
The ```-v``` flag controls the verbosity of the output.
It is optional and defaults to 0 and goes until 2.

If you do not specify ```--tls-root``` or ```--smime-root```, and/or ```--root-ca-file```, the program will use the system's root CA certificates only.
For all other cases, the system root CA certificates will be used in addition to the specified root CA certificates.

The ```--tls-root``` and ```--smime-root``` flags are mutually exclusive.
According to Mozilla, "an application that uses a root store for a purpose other than what the store was created for has a critical security vulnerability.
This is no different than failing to validate a certificate at all" [1].

[1] https://blog.mozilla.org/security/2021/05/10/beware-of-applications-misusing-root-stores/

## Input and Output
The input is in parquet format and has the following schema:
There are 2 different input types the program accepts: csv and parquet.

The input in parquet format and has the following schema:
```
root
|-- id: integer (nullable = true)
Expand All @@ -25,13 +43,19 @@ root
```
Where ```chain``` is an array of PEM-encoded X.509 certificates.

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 output is in parquet format and has the following schema:
```
root
|-- id: integer (nullable = true)
|-- chain: array (nullable = true)
| |-- element: string (containsNull = true)
|-- is_valid: boolean (nullable = true)
|-- error: string (nullable = true)
```

The ```is_valid``` field is ```true``` if the chain is valid and ```false``` otherwise for the given PEM certificates.
The ```error``` field contains the error message if the chain is invalid and ```null``` otherwise.
Binary file modified example/output-sample.parquet
Binary file not shown.
166 changes: 73 additions & 93 deletions input/input.go
Original file line number Diff line number Diff line change
@@ -1,110 +1,90 @@
package input

import (
"encoding/csv"
"encoding/json"
"github.com/rs/zerolog/log"
"github.com/xitongsys/parquet-go-source/local"
"github.com/xitongsys/parquet-go/reader"
"github.com/xitongsys/parquet-go/source"
"os"
"strconv"
"strings"
"encoding/json"
"github.com/gustavoluvizotto/cert-validator/misc"
"github.com/rs/zerolog/log"
"github.com/xitongsys/parquet-go-source/local"
"github.com/xitongsys/parquet-go/reader"
"github.com/xitongsys/parquet-go/source"
"strconv"
"strings"
)

type CertChain struct {
Id int32 `parquet:"name=id, type=INT32"`
Chain []string `parquet:"name=chain, type=LIST, valuetype=BYTE_ARRAY, valueconvertedtype=UTF8"`
Id int32 `parquet:"name=id, type=INT32"`
Chain []string `parquet:"name=chain, type=LIST, valuetype=BYTE_ARRAY, valueconvertedtype=UTF8"`
}

func LoadCsv(fileName string) []CertChain {
f, err := os.Open(fileName)
if err != nil {
log.Fatal().Str("fileName", fileName).Str("error", err.Error()).Msg("Unable to read input file")
}
defer func(f *os.File) {
err := f.Close()
if err != nil {
log.Fatal().Str("error", err.Error()).Msg("Failed to close the input file")
}
}(f)
records, err := misc.LoadCsv(fileName)
if err != nil {
return nil
}

csvReader := csv.NewReader(f)
// loop through records and convert to CertChain
n := len(records)
certChains := make([]CertChain, n)
for i, v := range records {
id, _ := strconv.Atoi(v[0])
var pemCerts []string
jsonInput := strings.ReplaceAll(v[1], "'", "\"")
if err := json.Unmarshal([]byte(jsonInput), &pemCerts); err != nil {
log.Fatal().Str("error", err.Error()).Msg("Unable to parse JSON input")
return nil
}
certChains[i] = CertChain{
Id: int32(id),
Chain: pemCerts,
}
}

// ignore header line
_, err = csvReader.Read()
if err != nil {
log.Fatal().Str("fileName", fileName).Str("error", err.Error()).Msg("Unable to parse CSV file")
}
// read all records
records, err := csvReader.ReadAll()
if err != nil {
log.Fatal().Str("fileName", fileName).Str("error", err.Error()).Msg("Unable to parse CSV file")
}

// loop through records and convert to CertChain
n := len(records)
certChains := make([]CertChain, n)
for i, v := range records {
id, _ := strconv.Atoi(v[0])
var pemCerts []string
jsonInput := strings.ReplaceAll(v[1], "'", "\"")
if err := json.Unmarshal([]byte(jsonInput), &pemCerts); err != nil {
log.Fatal().Str("error", err.Error()).Msg("Unable to parse JSON input")
return nil
}
certChains[i] = CertChain{
Id: int32(id),
Chain: pemCerts,
}
}

return certChains
return certChains
}

func LoadParquet(fileName string) []CertChain {
// FIXME this is not working
fr, err := local.NewLocalFileReader(fileName)
if err != nil {
log.Fatal().Str("file", fileName).Str("error", err.Error()).Msg("Failed to open file")
return nil
}
defer func(fr source.ParquetFile) {
err := fr.Close()
if err != nil {
log.Fatal().Str("error", err.Error()).Msg("Failed to close the input file")
}
}(fr)
// FIXME this is not working
fr, err := local.NewLocalFileReader(fileName)
if err != nil {
log.Fatal().Str("file", fileName).Str("error", err.Error()).Msg("Failed to open file")
return nil
}
defer func(fr source.ParquetFile) {
err := fr.Close()
if err != nil {
log.Fatal().Str("error", err.Error()).Msg("Failed to close the input file")
}
}(fr)

pr, err := reader.NewParquetReader(fr, new(CertChain), 4)
if err != nil {
log.Fatal().Str("error", err.Error()).Msg("Failed to create reader")
return nil
}
defer pr.ReadStop()
pr, err := reader.NewParquetReader(fr, new(CertChain), 4)
if err != nil {
log.Fatal().Str("error", err.Error()).Msg("Failed to create reader")
return nil
}
defer pr.ReadStop()

// read parquet file
num := int(pr.GetNumRows())
//res := make([]interface{}, num)
res, err := pr.ReadByNumber(num)
if err != nil {
log.Fatal().Str("error", err.Error()).Msg("Can't read")
return nil
}
/*
for i := 0; i < num; i++ {
certChain := make([]CertChain, 1)
if err = pr.Read(&certChain); err != nil {
log.Fatal().Str("error", err.Error()).Msg("Can't read")
return nil
}
res[i] = certChain
break
}
*/
certChains := make([]CertChain, len(res))
for i, v := range res {
certChains[i] = v.(CertChain)
}
return certChains
// read parquet file
num := int(pr.GetNumRows())
//res := make([]interface{}, num)
res, err := pr.ReadByNumber(num)
if err != nil {
log.Fatal().Str("error", err.Error()).Msg("Can't read")
return nil
}
/*
for i := 0; i < num; i++ {
certChain := make([]CertChain, 1)
if err = pr.Read(&certChain); err != nil {
log.Fatal().Str("error", err.Error()).Msg("Can't read")
return nil
}
res[i] = certChain
break
}
*/
certChains := make([]CertChain, len(res))
for i, v := range res {
certChains[i] = v.(CertChain)
}
return certChains
}
Loading

0 comments on commit 918bb44

Please sign in to comment.