Skip to content

Commit

Permalink
add no duplicate flag
Browse files Browse the repository at this point in the history
  • Loading branch information
pete911 committed Dec 10, 2022
1 parent cd6ad17 commit 72b313f
Show file tree
Hide file tree
Showing 8 changed files with 178 additions and 22 deletions.
25 changes: 13 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,19 @@ certinfo [flags] [<file>|<host:port> ...]
- **stdin** `echo "<cert-content>" | certinfo`

```
+-----------------------------------------------------------------------------------------------------------------+
| optional flags |
+-------------+---------------------------------------------------------------------------------------------------+
| -chains | whether to print verified chains as well (only applicable for host) |
| -expiry | print expiry of certificates |
| -insecure | whether a client verifies the server's certificate chain and host name (only applicable for host) |
| -no-expired | do not print expired certificates |
| -pem | whether to print pem as well |
| -pem-only | whether to print only pem (useful for downloading certs from host) |
| -version | certinfo version |
| -help | help |
+-----------+-----------------------------------------------------------------------------------------------------+
+-------------------------------------------------------------------------------------------------------------------+
| optional flags |
+---------------+---------------------------------------------------------------------------------------------------+
| -chains | whether to print verified chains as well (only applicable for host) |
| -expiry | print expiry of certificates |
| -insecure | whether a client verifies the server's certificate chain and host name (only applicable for host) |
| -no-duplicate | do not print duplicate certificates |
| -no-expired | do not print expired certificates |
| -pem | whether to print pem as well |
| -pem-only | whether to print only pem (useful for downloading certs from host) |
| -version | certinfo version |
| -help | help |
+---------------+---------------------------------------------------------------------------------------------------+
```

Flags can be set as env. variable as well (`CERTINFO_<FLAG>=true` e.g. `CERTINFO_INSECURE=true`) and can be then
Expand Down
21 changes: 12 additions & 9 deletions flag.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,16 @@ import (
)

type Flags struct {
Usage func()
Expiry bool
NoExpired bool
Insecure bool
Chains bool
Pem bool
PemOnly bool
Version bool
Args []string
Usage func()
Expiry bool
NoDuplicate bool
NoExpired bool
Insecure bool
Chains bool
Pem bool
PemOnly bool
Version bool
Args []string
}

func ParseFlags() (Flags, error) {
Expand All @@ -25,6 +26,8 @@ func ParseFlags() (Flags, error) {
flagSet := flag.NewFlagSet(os.Args[0], flag.ContinueOnError)
flagSet.BoolVar(&flags.Expiry, "expiry", getBoolEnv("CERTINFO_EXPIRY", false),
"print expiry of certificates")
flagSet.BoolVar(&flags.NoDuplicate, "no-duplicate", getBoolEnv("CERTINFO_NO_DUPLICATE", false),
"do not print duplicate certificates")
flagSet.BoolVar(&flags.NoExpired, "no-expired", getBoolEnv("CERTINFO_NO_EXPIRED", false),
"do not print expired certificates")
flagSet.BoolVar(&flags.Insecure, "insecure", getBoolEnv("CERTINFO_INSECURE", false),
Expand Down
3 changes: 3 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ func main() {
if flags.NoExpired {
certificatesFiles = certificatesFiles.RemoveExpired()
}
if flags.NoDuplicate {
certificatesFiles = certificatesFiles.RemoveDuplicates()
}
if flags.Expiry {
PrintCertificatesExpiry(certificatesFiles)
return
Expand Down
15 changes: 14 additions & 1 deletion pkg/cert/cert.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,19 @@ func (c Certificates) RemoveExpired() Certificates {
return out
}

func (c Certificates) RemoveDuplicates() Certificates {
var out Certificates
savedSet := map[string]struct{}{}
for i := range c {
stringPem := string(c[i].ToPEM())
if _, ok := savedSet[stringPem]; !ok {
savedSet[stringPem] = struct{}{}
out = append(out, c[i])
}
}
return out
}

type Certificate struct {
// position of certificate in the chain, starts with 1
position int
Expand Down Expand Up @@ -96,7 +109,7 @@ func (c Certificate) ToPEM() []byte {
func (c Certificate) SubjectString() string {

if c.err != nil {
return "-"
return fmt.Sprintf("ERROR: block at position %d: %v", c.position, c.err)
}
return c.x509Certificate.Subject.String()
}
Expand Down
48 changes: 48 additions & 0 deletions pkg/cert/cert_test.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,47 @@
package cert

import (
"bytes"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"os"
"path/filepath"
"testing"
"time"
)

func TestFromBytes(t *testing.T) {
t.Run("given valid PEM certificate, then certificate is loaded", func(t *testing.T) {
certificates := loadTestCertificates(t, "cert.pem")
require.Equal(t, 1, len(certificates))
assert.Equal(t, 1, certificates[0].position)
assert.Equal(t, "CN=DigiCert Global Root G2,OU=www.digicert.com,O=DigiCert Inc,C=US", certificates[0].SubjectString())
assert.Nil(t, certificates[0].err)
})

t.Run("given valid PEM bundle, then all certificates are loaded", func(t *testing.T) {
certificates := loadTestCertificates(t, "bundle.pem")
require.Equal(t, 2, len(certificates))
assert.Equal(t, "CN=DigiCert Global Root G2,OU=www.digicert.com,O=DigiCert Inc,C=US", certificates[0].SubjectString())
assert.Equal(t, "CN=GTS Root R1,O=Google Trust Services LLC,C=US", certificates[1].SubjectString())
})
}

func TestCertificates_RemoveDuplicates(t *testing.T) {
t.Run("given duplicate PEM certificate, when remove duplicates is called, then they are removed", func(t *testing.T) {
bundle := bytes.Join([][]byte{
loadTestFile(t, "bundle.pem"),
loadTestFile(t, "bundle.pem"),
}, []byte("\n"))
certificates, err := FromBytes(bundle)
require.NoError(t, err)

require.Equal(t, 4, len(certificates))
noDuplicates := certificates.RemoveDuplicates()
require.Equal(t, 2, len(noDuplicates))
})
}

func Test_expiryFormat(t *testing.T) {
t.Run("given certificate expiry is more than a year then year is returned as well", func(t *testing.T) {
v := expiryFormat(getTime(3, 2, 7, 5, 25))
Expand Down Expand Up @@ -35,6 +71,18 @@ func Test_expiryFormat(t *testing.T) {

// --- helper functions ---

func loadTestCertificates(t *testing.T, file string) Certificates {
certificates, err := FromBytes(loadTestFile(t, file))
require.NoError(t, err)
return certificates
}

func loadTestFile(t *testing.T, file string) []byte {
b, err := os.ReadFile(filepath.Join("testdata", file))
require.NoError(t, err)
return b
}

func getTime(years, months, days, hours, minutes int) time.Time {
return time.Now().AddDate(years, months, days).
Add(time.Hour*time.Duration(hours) + time.Minute*time.Duration(minutes))
Expand Down
13 changes: 13 additions & 0 deletions pkg/cert/location.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@ func (c CertificateLocations) RemoveExpired() CertificateLocations {
return out
}

func (c CertificateLocations) RemoveDuplicates() CertificateLocations {
var out CertificateLocations
for i := range c {
out = append(out, c[i].RemoveDuplicates())
}
return out
}

type CertificateLocation struct {
TLSVersion uint16 // only applicable for network certificates
Path string
Expand All @@ -37,6 +45,11 @@ func (c CertificateLocation) RemoveExpired() CertificateLocation {
return c
}

func (c CertificateLocation) RemoveDuplicates() CertificateLocation {
c.Certificates = c.Certificates.RemoveDuplicates()
return c
}

func LoadCertificatesFromNetwork(addr string, tlsSkipVerify bool) (CertificateLocation, error) {

conn, err := tls.DialWithDialer(&net.Dialer{Timeout: tlsDialTimeout}, "tcp", addr, &tls.Config{InsecureSkipVerify: tlsSkipVerify})
Expand Down
53 changes: 53 additions & 0 deletions pkg/cert/testdata/bundle.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
-----BEGIN CERTIFICATE-----
MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH
MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT
MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG
9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI
2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx
1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ
q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz
tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ
vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP
BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV
5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY
1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4
NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG
Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91
8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe
pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl
MrY=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFWjCCA0KgAwIBAgIQbkepxUtHDA3sM9CJuRz04TANBgkqhkiG9w0BAQwFADBH
MQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExM
QzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIy
MDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNl
cnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwggIiMA0GCSqGSIb3DQEB
AQUAA4ICDwAwggIKAoICAQC2EQKLHuOhd5s73L+UPreVp0A8of2C+X0yBoJx9vaM
f/vo27xqLpeXo4xL+Sv2sfnOhB2x+cWX3u+58qPpvBKJXqeqUqv4IyfLpLGcY9vX
mX7wCl7raKb0xlpHDU0QM+NOsROjyBhsS+z8CZDfnWQpJSMHobTSPS5g4M/SCYe7
zUjwTcLCeoiKu7rPWRnWr4+wB7CeMfGCwcDfLqZtbBkOtdh+JhpFAz2weaSUKK0P
fyblqAj+lug8aJRT7oM6iCsVlgmy4HqMLnXWnOunVmSPlk9orj2XwoSPwLxAwAtc
vfaHszVsrBhQf4TgTM2S0yDpM7xSma8ytSmzJSq0SPly4cpk9+aCEI3oncKKiPo4
Zor8Y/kB+Xj9e1x3+naH+uzfsQ55lVe0vSbv1gHR6xYKu44LtcXFilWr06zqkUsp
zBmkMiVOKvFlRNACzqrOSbTqn3yDsEB750Orp2yjj32JgfpMpf/VjsPOS+C12LOO
Rc92wO1AK/1TD7Cn1TsNsYqiA94xrcx36m97PtbfkSIS5r762DL8EGMUUXLeXdYW
k70paDPvOmbsB4om3xPXV2V4J95eSRQAogB/mqghtqmxlbCluQ0WEdrHbEg8QOB+
DVrNVjzRlwW5y0vtOUucxD/SVRNuJLDWcfr0wbrM7Rv1/oFB2ACYPTrIrnqYNxgF
lQIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV
HQ4EFgQU5K8rJnEaK0gnhS9SZizv8IkTcT4wDQYJKoZIhvcNAQEMBQADggIBADiW
Cu49tJYeX++dnAsznyvgyv3SjgofQXSlfKqE1OXyHuY3UjKcC9FhHb8owbZEKTV1
d5iyfNm9dKyKaOOpMQkpAWBz40d8U6iQSifvS9efk+eCNs6aaAyC58/UEBZvXw6Z
XPYfcX3v73svfuo21pdwCxXu11xWajOl40k4DLh9+42FpLFZXvRq4d2h9mREruZR
gyFmxhE+885H7pwoHyXa/6xmld01D1zvICxi/ZG6qcz8WpyTgYMpl0p8WnK0OdC3
d8t5/Wk6kjftbjhlRn7pYL15iJdfOBL07q9bgsiG1eGZbYwE8na6SfZu6W0eX6Dv
J4J2QPim01hcDyxC2kLGe4g0x8HYRZvBPsVhHdljUEn2NIVq4BjFbkerQUIpm/Zg
DdIx02OYI5NaAIFItO/Nis3Jz5nu2Z6qNuFoS3FJFDYoOj0dzpqPJeaAcWErtXvM
+SUWgeExX6GjfhaknBZqlxi9dnKlC54dNuYvoS++cJEPqOba+MSSQGwlfnuzCdyy
F62ARPBopY+Udf90WuioAnwMCeKpSwughQtiue+hMZL77/ZRBIls6Kl0obsXs7X9
SQ98POyDGCBDTtWTurQ0sR8WNh8M5mQ5Fkzc4P4dyKliPUDqysU0ArSuiYgzNdws
E3PYJ/HQcu51OyLemGhmW/HGY0dVHLqlCFF1pkgl
-----END CERTIFICATE-----
22 changes: 22 additions & 0 deletions pkg/cert/testdata/cert.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
-----BEGIN CERTIFICATE-----
MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH
MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT
MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG
9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI
2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx
1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ
q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz
tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ
vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP
BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV
5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY
1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4
NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG
Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91
8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe
pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl
MrY=
-----END CERTIFICATE-----

0 comments on commit 72b313f

Please sign in to comment.