Skip to content

Commit

Permalink
refactor and formatting
Browse files Browse the repository at this point in the history
  • Loading branch information
pete911 committed Dec 18, 2024
1 parent c5912ba commit bb2a9c4
Show file tree
Hide file tree
Showing 13 changed files with 402 additions and 315 deletions.
3 changes: 3 additions & 0 deletions flag.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ type Flags struct {
Extensions bool
Pem bool
PemOnly bool
Verbose bool
Version bool
Clipboard bool
Args []string
Expand Down Expand Up @@ -57,6 +58,8 @@ func ParseFlags() (Flags, error) {
flagSet.BoolVar(&flags.Clipboard, "clipboard", false,
"read input from clipboard")
}
flagSet.BoolVar(&flags.Verbose, "verbose", getBoolEnv("CERTINFO_VERBOSE", false),
"verbose logging")
flagSet.BoolVar(&flags.Version, "version", getBoolEnv("CERTINFO_VERSION", false),
"certinfo version")

Expand Down
17 changes: 14 additions & 3 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package main
import (
"fmt"
"github.com/pete911/certinfo/pkg/cert"
"github.com/pete911/certinfo/pkg/print"
"log/slog"
"os"
"strconv"
"strings"
Expand All @@ -18,6 +20,7 @@ func main() {
fmt.Println(err.Error())
os.Exit(1)
}
setLogger(flags.Verbose)

if flags.Version {
fmt.Println(Version)
Expand All @@ -41,14 +44,22 @@ func main() {
certificatesFiles = certificatesFiles.SortByExpiry()
}
if flags.Expiry {
PrintCertificatesExpiry(certificatesFiles)
print.Expiry(certificatesFiles)
return
}
if flags.PemOnly {
PrintPemOnly(certificatesFiles, flags.Chains)
print.Pem(certificatesFiles, flags.Chains)
return
}
PrintCertificatesLocations(certificatesFiles, flags.Chains, flags.Pem, flags.Extensions)
print.Locations(certificatesFiles, flags.Chains, flags.Pem, flags.Extensions)
}

func setLogger(verbose bool) {
level := slog.LevelError
if verbose {
level = slog.LevelDebug
}
slog.SetDefault(slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: level})))
}

func LoadCertificatesLocations(flags Flags) cert.CertificateLocations {
Expand Down
185 changes: 116 additions & 69 deletions pkg/cert/cert.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,46 @@ import (
"encoding/pem"
"errors"
"fmt"
"github.com/icza/gox/timex"
"log/slog"
"slices"
"strings"
"time"
)

const certificateBlockType = "CERTIFICATE"

var (
// order is important!
keyUsages = []string{
"Digital Signature",
"Content Commitment",
"Key Encipherment",
"Data Encipherment",
"Key Agreement",
"Cert Sign",
"CRL Sign",
"Encipher Only",
"Decipher Only",
}
// order is important!
extKeyUsages = []string{
"Any",
"Server Auth",
"Client Auth",
"Code Signing",
"Email Protection",
"IPSEC End System",
"IPSEC Tunnel",
"IPSEC User",
"Time Stamping",
"OCSP Signing",
"Microsoft Server Gated Crypto",
"Netscape Server Gated Crypto",
"Microsoft Commercial Code Signing",
"Microsoft Kernel Code Signing",
}
)

type Certificates []Certificate

func (c Certificates) RemoveExpired() Certificates {
Expand Down Expand Up @@ -150,101 +182,116 @@ func (c Certificate) SubjectString() string {
return subject.String()
}

func (c Certificate) ExpiryString() string {

func (c Certificate) Error() error {
if c.err != nil {
return "-"
}
expiry := formatExpiry(c.x509Certificate.NotAfter)
if c.IsExpired() {
return fmt.Sprintf("EXPIRED %s ago", expiry)
return fmt.Errorf("ERROR: block at position %d: %v", c.position, c.err)
}
return expiry
return nil
}

func (c Certificate) String() string {

if c.err != nil {
return fmt.Sprintf("ERROR: block at position %d: %v", c.position, c.err)
}

dnsNames := strings.Join(c.x509Certificate.DNSNames, ", ")
func (c Certificate) DNSNames() []string {
return c.x509Certificate.DNSNames
}

func (c Certificate) IPAddresses() []string {
var ips []string
for _, ip := range c.x509Certificate.IPAddresses {
ips = append(ips, fmt.Sprintf("%s", ip))
}
ipAddresses := strings.Join(ips, ", ")
return ips
}

keyUsage := KeyUsageToString(c.x509Certificate.KeyUsage)
extKeyUsage := ExtKeyUsageToString(c.x509Certificate.ExtKeyUsage)
var authorityKeyId string
if c.x509Certificate.AuthorityKeyId != nil {
authorityKeyId = formatHexArray(c.x509Certificate.AuthorityKeyId)
}
func (c Certificate) Version() int {
return c.x509Certificate.Version
}

return strings.Join([]string{
fmt.Sprintf("Version: %d", c.x509Certificate.Version),
fmt.Sprintf("Serial Number: %s", formatHexArray(c.x509Certificate.SerialNumber.Bytes())),
fmt.Sprintf("Signature Algorithm: %s", c.x509Certificate.SignatureAlgorithm),
fmt.Sprintf("Type: %s", c.Type()),
fmt.Sprintf("Issuer: %s", c.x509Certificate.Issuer),
fmt.Sprintf("Validity\n Not Before: %s\n Not After : %s",
ValidityFormat(c.x509Certificate.NotBefore),
ValidityFormat(c.x509Certificate.NotAfter)),
fmt.Sprintf("Subject: %s", c.SubjectString()),
fmt.Sprintf("DNS Names: %s", dnsNames),
fmt.Sprintf("IP Addresses: %s", ipAddresses),
fmt.Sprintf("Authority Key Id: %s", authorityKeyId),
fmt.Sprintf("Subject Key Id : %s", formatHexArray(c.x509Certificate.SubjectKeyId)),
fmt.Sprintf("Key Usage: %s", strings.Join(keyUsage, ", ")),
fmt.Sprintf("Ext Key Usage: %s", strings.Join(extKeyUsage, ", ")),
fmt.Sprintf("CA: %t", c.x509Certificate.IsCA),
}, "\n")
func (c Certificate) SerialNumber() string {
return formatHexArray(c.x509Certificate.SerialNumber.Bytes())
}

func (c Certificate) Type() string {
if c.x509Certificate.AuthorityKeyId == nil || bytes.Equal(c.x509Certificate.AuthorityKeyId, c.x509Certificate.SubjectKeyId) {
return "root"
func (c Certificate) SignatureAlgorithm() string {
return c.x509Certificate.SignatureAlgorithm.String()
}

func (c Certificate) Issuer() string {
return c.x509Certificate.Issuer.String()
}

func (c Certificate) NotBefore() time.Time {
return c.x509Certificate.NotBefore
}

func (c Certificate) NotAfter() time.Time {
return c.x509Certificate.NotAfter
}

func (c Certificate) AuthorityKeyId() string {
if c.x509Certificate.AuthorityKeyId != nil {
return formatHexArray(c.x509Certificate.AuthorityKeyId)
}
return ""
}

if c.x509Certificate.IsCA {
return "intermediate"
func (c Certificate) SubjectKeyId() string {
if c.x509Certificate.SubjectKeyId != nil {
return formatHexArray(c.x509Certificate.SubjectKeyId)
}
return "end-entity"
return ""
}

func (c Certificate) Extensions() string {
var lines []string
for _, v := range ToExtensions(c.x509Certificate.Extensions) {
name := fmt.Sprintf("%s (%s)", v.Name, v.Oid)
if v.Critical {
name = fmt.Sprintf("%s [critical]", name)
}
lines = append(lines, name)
for _, line := range v.Values {
lines = append(lines, fmt.Sprintf(" %s", line))
func (c Certificate) IsCA() bool {
return c.x509Certificate.IsCA
}

func (c Certificate) KeyUsage() []string {
var out []string
for i, v := range keyUsages {
bitmask := 1 << i
if (int(c.x509Certificate.KeyUsage) & bitmask) == 0 {
continue
}
out = append(out, v)
}
return strings.Join(lines, "\n")
return out
}

func formatExpiry(t time.Time) string {
// ExtKeyUsage extended key usage string representation
func (c Certificate) ExtKeyUsage() []string {

year, month, day, hour, minute, _ := timex.Diff(time.Now(), t)
if year != 0 {
return fmt.Sprintf("%d years %d months %d days %d hours %d minutes", year, month, day, hour, minute)
var extendedKeyUsageString []string
for _, v := range c.x509Certificate.ExtKeyUsage {
extendedKeyUsageString = append(extendedKeyUsageString, extKeyUsages[v])
}
if month != 0 {
return fmt.Sprintf("%d months %d days %d hours %d minutes", month, day, hour, minute)
return extendedKeyUsageString
}

func (c Certificate) Type() string {
if c.x509Certificate.AuthorityKeyId == nil || bytes.Equal(c.x509Certificate.AuthorityKeyId, c.x509Certificate.SubjectKeyId) {
return "root"
}
if day != 0 {
return fmt.Sprintf("%d days %d hours %d minutes", day, hour, minute)

if c.x509Certificate.IsCA {
return "intermediate"
}
if hour != 0 {
return fmt.Sprintf("%d hours %d minutes", hour, minute)
return "end-entity"
}
func (c Certificate) Extensions() []Extension {
var out []Extension
for _, v := range c.x509Certificate.Extensions {
name, value, err := parseExtension(v)
if err != nil {
// log error and set error as value
slog.Error(fmt.Sprintf("certificate at position %d: extension %s (%s): %v", c.position, name, v.Id.String(), err))
value = []string{err.Error()}
}
out = append(out, Extension{
Name: name,
Oid: v.Id.String(),
Critical: v.Critical,
Values: value,
})
}
return fmt.Sprintf("%d minutes", minute)
return out
}

func formatHexArray(b []byte) string {
Expand Down
28 changes: 0 additions & 28 deletions pkg/cert/cert_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"crypto/x509"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"strings"
"testing"
"time"
)
Expand Down Expand Up @@ -55,33 +54,6 @@ func TestCertificates_SortByExpiry(t *testing.T) {
})
}

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 := formatExpiry(getTime(3, 2, 7, 5, 25))
assert.True(t, strings.HasPrefix(v, "3 years 2 months "))
})

t.Run("given certificate expiry is less than a year then year is not returned", func(t *testing.T) {
v := formatExpiry(getTime(0, 2, 7, 5, 25))
assert.True(t, strings.HasPrefix(v, "2 months "))
})

t.Run("given certificate expiry is less than a month then year and month is not returned", func(t *testing.T) {
v := formatExpiry(getTime(0, 0, 7, 5, 25))
assert.Equal(t, "7 days 5 hours 25 minutes", v)
})

t.Run("given certificate expiry is less than a day then year, month and day is not returned", func(t *testing.T) {
v := formatExpiry(getTime(0, 0, 0, 5, 25))
assert.Equal(t, "5 hours 25 minutes", v)
})

t.Run("given certificate expiry is less than an hour then only minutes are returned", func(t *testing.T) {
v := formatExpiry(getTime(0, 0, 0, 0, 25))
assert.Equal(t, "25 minutes", v)
})
}

func Test_rootIdentification(t *testing.T) {
t.Run("given certificate issuer is identical to subject but authority key id is set then identify as root", func(t *testing.T) {
certificate := loadTestCertificates(t, "root_with_authority_key_id.pem")
Expand Down
Loading

0 comments on commit bb2a9c4

Please sign in to comment.