Skip to content

Commit

Permalink
initial extensions
Browse files Browse the repository at this point in the history
  • Loading branch information
pete911 committed Oct 11, 2024
1 parent 3fa99e8 commit 5f03d2f
Show file tree
Hide file tree
Showing 3 changed files with 146 additions and 9 deletions.
20 changes: 16 additions & 4 deletions pkg/cert/cert.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"encoding/hex"
"encoding/pem"
"errors"
"fmt"
Expand Down Expand Up @@ -153,7 +154,7 @@ func (c Certificate) ExpiryString() string {
if c.err != nil {
return "-"
}
expiry := expiryFormat(c.x509Certificate.NotAfter)
expiry := formatExpiry(c.x509Certificate.NotAfter)
if c.IsExpired() {
return fmt.Sprintf("EXPIRED %s ago", expiry)
}
Expand All @@ -179,7 +180,7 @@ func (c Certificate) String() string {

return strings.Join([]string{
fmt.Sprintf("Version: %d", c.x509Certificate.Version),
fmt.Sprintf("Serial Number: %d", c.x509Certificate.SerialNumber),
fmt.Sprintf("Serial Number: %s", formatHexArray(c.x509Certificate.SerialNumber.Bytes())),
fmt.Sprintf("Signature Algorithm: %s", c.x509Certificate.SignatureAlgorithm),
fmt.Sprintf("Type: %s", CertificateType(c.x509Certificate)),
fmt.Sprintf("Issuer: %s", c.x509Certificate.Issuer),
Expand All @@ -190,14 +191,14 @@ func (c Certificate) String() string {
fmt.Sprintf("DNS Names: %s", dnsNames),
fmt.Sprintf("IP Addresses: %s", ipAddresses),
fmt.Sprintf("Authority Key Id: %x", c.x509Certificate.AuthorityKeyId),
fmt.Sprintf("Subject Key Id : %x", c.x509Certificate.SubjectKeyId),
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 expiryFormat(t time.Time) string {
func formatExpiry(t time.Time) string {

year, month, day, hour, minute, _ := timex.Diff(time.Now(), t)
if year != 0 {
Expand All @@ -214,3 +215,14 @@ func expiryFormat(t time.Time) string {
}
return fmt.Sprintf("%d minutes", minute)
}

func formatHexArray(b []byte) string {

buf := make([]byte, 0, 3*len(b))
x := buf[1*len(b) : 3*len(b)]
hex.Encode(x, b)
for i := 0; i < len(x); i += 2 {
buf = append(buf, x[i], x[i+1], ':')
}
return string(buf[:len(buf)-1])
}
20 changes: 15 additions & 5 deletions pkg/cert/cert_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package cert

import (
"crypto/x509"
"fmt"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"strings"
Expand Down Expand Up @@ -57,27 +58,27 @@ 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 := expiryFormat(getTime(3, 2, 7, 5, 25))
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 := expiryFormat(getTime(0, 2, 7, 5, 25))
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 := expiryFormat(getTime(0, 0, 7, 5, 25))
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 := expiryFormat(getTime(0, 0, 0, 5, 25))
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 := expiryFormat(getTime(0, 0, 0, 0, 25))
v := formatExpiry(getTime(0, 0, 0, 0, 25))
assert.Equal(t, "25 minutes", v)
})
}
Expand Down Expand Up @@ -109,3 +110,12 @@ func Test_intermediateIdentification(t *testing.T) {
require.Equal(t, "intermediate", CertificateType(certificate[0].x509Certificate))
})
}

func TestExtensionsManual(t *testing.T) {
// 4E:22:54:20:18:95:E6:E3:6E:E6:0F:FA:FA:B9:12:ED:06:17:8F:39
certificates := loadTestCertificates(t, "cert.pem")
extensions := ToExtensions(certificates[0].x509Certificate.Extensions)
for _, extension := range extensions {
fmt.Printf("%+v\n", extension)
}
}
115 changes: 115 additions & 0 deletions pkg/cert/extensions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package cert

import (
"crypto/x509/pkix"
"encoding/asn1"
"fmt"
"strings"
)

type Extension struct {
Name string
Critical bool
Value string
}

func ToExtensions(in []pkix.Extension) []Extension {

var out []Extension
for _, v := range in {
name, value := parseExtension(v)
out = append(out, Extension{
Name: name,
Critical: v.Critical,
Value: value,
})
}
return out
}

func parseExtension(in pkix.Extension) (string, string) {
if fn, ok := extensionsByOid[in.Id.String()]; ok {
return fn(in.Value)
}
return in.Id.String(), "-"
}

var extensionsByOid = map[string]func(in []byte) (string, string){
//"2.5.29.35": parseAuthorityKeyIdentifier,
"2.5.29.14": parseSubjectKeyIdentifier,
"2.5.29.15": parseKeyUsage,
//"2.5.29.32": parseCertificatePolicies,
//"2.5.29.": parsePolicyMappings,
//"2.5.29.": parseSubjectAlternativeName,
//"2.5.29.": parseIssuerAlternativeName,
//"2.5.29.": parseSubjectDirectoryAttributes,
"2.5.29.19": parseBasicConstraints,
//"2.5.29.": parseNameConstraints,
//"2.5.29.": parsePolicyConstraints,
//"2.5.29.": parseExtendedKeyUsage,
//"2.5.29.": parseCRLDistributionPoints,
//"2.5.29.": parseInhibitAnyPolicy,
//"2.5.29.": parseFreshestCRL,
// private internet extensions
//"": parseAuthorityInformationAccess,
//"": parseSubjectInformationAccess,
}

func parseSubjectKeyIdentifier(in []byte) (string, string) {
name := "Subject Key Identifier"
out := asn1.RawValue{Tag: asn1.TagOctetString}
if _, err := asn1.Unmarshal(in, &out); err != nil {
return name, err.Error()
}
return name, formatHexArray(out.Bytes)
}

func parseKeyUsage(in []byte) (string, string) {
name := "Key Usage"
var out asn1.BitString
if _, err := asn1.Unmarshal(in, &out); err != nil {
return name, err.Error()
}
return name, strings.Join(toKeyUsage(out), ", ")
}

func parseBasicConstraints(in []byte) (string, string) {
name := "Basic Constraints"
out := struct {
CA bool `asn1:"optional"`
PathLenConstraint int `asn1:"optional"`
}{}

if _, err := asn1.Unmarshal(in, &out); err != nil {
return name, err.Error()
}

fields := []string{fmt.Sprintf("CA: %t", out.CA)}
if out.PathLenConstraint != 0 {
fields = append(fields, fmt.Sprintf("PathLenConstraint: %d", out.PathLenConstraint))
}
return name, strings.Join(fields, ", ")
}

// order is important!
var keyUsage = []string{
"Digital Signature",
"Content Commitment", // renamed from non repudiation
"Key Encipherment",
"Data Encipherment",
"Key Agreement",
"Key Cert Sign",
"CRLs Sign",
"Encipher Only",
"Decipher Only",
}

func toKeyUsage(in asn1.BitString) []string {
var out []string
for i, v := range keyUsage {
if in.At(i) != 0 {
out = append(out, v)
}
}
return out
}

0 comments on commit 5f03d2f

Please sign in to comment.