Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions flag.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ type Flags struct {
Insecure bool
Chains bool
Extensions bool
PublicKey bool
Signature bool
Pem bool
PemOnly bool
Expand Down Expand Up @@ -51,6 +52,8 @@ func ParseFlags() (Flags, error) {
"whether to print verified chains as well (only applicable for host)")
flagSet.BoolVar(&flags.Extensions, "extensions", getBoolEnv("CERTINFO_EXTENSIONS", false),
"whether to print extensions")
flagSet.BoolVar(&flags.PublicKey, "public-key", getBoolEnv("CERTINFO_PUBLIC_KEY", false),
"whether to print public key info")
flagSet.BoolVar(&flags.Signature, "signature", getBoolEnv("CERTINFO_SIGNATURE", false),
"whether to print signature")
flagSet.BoolVar(&flags.Pem, "pem", getBoolEnv("CERTINFO_PEM", false),
Expand Down
2 changes: 1 addition & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ func main() {
print.Pem(certificatesFiles, flags.Chains)
return
}
print.Locations(certificatesFiles, flags.Chains, flags.Pem, flags.Extensions, flags.Signature)
print.Locations(certificatesFiles, flags.Chains, flags.Pem, flags.Extensions, flags.PublicKey, flags.Signature)
}

func setLogger(verbose bool) {
Expand Down
34 changes: 16 additions & 18 deletions pkg/cert/cert.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ import (
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"encoding/hex"
"encoding/pem"
"errors"
"fmt"
"github.com/pete911/certinfo/pkg/cert/format"
"log/slog"
"slices"
"strings"
Expand Down Expand Up @@ -211,13 +211,24 @@ func (c Certificate) Version() int {
}

func (c Certificate) SerialNumber() string {
return formatHexArray(c.x509Certificate.SerialNumber.Bytes())
return format.HexArray(c.x509Certificate.SerialNumber.Bytes())
}

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

func (c Certificate) SubjectPublicKeyInfo() string {
info, err := ToSubjectPublicKeyInfo(c.x509Certificate.RawSubjectPublicKeyInfo)
if err != nil {
return err.Error()
}
if info != nil {
return info.String()
}
return ""
}

func (c Certificate) Issuer() string {
return c.x509Certificate.Issuer.String()
}
Expand All @@ -232,14 +243,14 @@ func (c Certificate) NotAfter() time.Time {

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

func (c Certificate) SubjectKeyId() string {
if c.x509Certificate.SubjectKeyId != nil {
return formatHexArray(c.x509Certificate.SubjectKeyId)
return format.HexArray(c.x509Certificate.SubjectKeyId)
}
return ""
}
Expand All @@ -249,7 +260,7 @@ func (c Certificate) PublicKeyAlgorithm() string {
}

func (c Certificate) Signature() string {
return formatHexArray(c.x509Certificate.Signature)
return format.HexArray(c.x509Certificate.Signature)
}

func (c Certificate) IsCA() bool {
Expand Down Expand Up @@ -306,16 +317,3 @@ func (c Certificate) Extensions() []Extension {
}
return out
}

func formatHexArray(b []byte) string {
if len(b) == 0 {
return ""
}
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 strings.ToUpper(string(buf[:len(buf)-1]))
}
5 changes: 3 additions & 2 deletions pkg/cert/extensions.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"crypto/x509/pkix"
"encoding/asn1"
"fmt"
"github.com/pete911/certinfo/pkg/cert/format"
"strings"
)

Expand Down Expand Up @@ -56,7 +57,7 @@ func parseAuthorityKeyIdentifier(in []byte) (string, []string, error) {
return name, nil, err
}

fields := []string{formatHexArray(out.KeyIdentifier)}
fields := []string{format.HexArray(out.KeyIdentifier)}
if out.AuthorityCertIssuer != nil {
v := strings.Join(out.AuthorityCertIssuer, ", ")
fields = append(fields, fmt.Sprintf("Authority Cert. Issuer: %s", v))
Expand All @@ -74,7 +75,7 @@ func parseSubjectKeyIdentifier(in []byte) (string, []string, error) {
if _, err := asn1.Unmarshal(in, &out); err != nil {
return name, nil, err
}
return name, []string{formatHexArray(out.Bytes)}, nil
return name, []string{format.HexArray(out.Bytes)}, nil
}

// KeyUsage ::= BIT STRING {
Expand Down
43 changes: 43 additions & 0 deletions pkg/cert/format/format.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package format

import (
"encoding/hex"
"strings"
"time"
)

func HexArray(b []byte) string {
if len(b) == 0 {
return ""
}
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 strings.ToUpper(string(buf[:len(buf)-1]))
}

func Validity(t time.Time) string {
// format for NotBefore and NotAfter fields to make output similar to openssl
return t.Format("Jan _2 15:04:05 2006 MST")
}

func SplitString(in, prefix string, size int) []string {
if len(in) <= size {
return []string{prefix + in}
}

var chunk string
var out []string
for {
in, chunk = in[size:], in[:size]
out = append(out, prefix+chunk)
if len(in) <= size {
out = append(out, prefix+in)
break
}
}
return out
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
package print
package format

import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"testing"
)

func Test_splitString(t *testing.T) {
func TestSplitString(t *testing.T) {
t.Run("given string when longer than size then it is split", func(t *testing.T) {
someString := "some long string that we want to split multiple times"
out := splitString(someString, " ", 6)
out := SplitString(someString, " ", 6)
for _, v := range out {
// cannot be more than size + prefix
assert.True(t, len(v) <= 8)
Expand All @@ -19,7 +19,7 @@ func Test_splitString(t *testing.T) {

t.Run("given string when shorter than size then it is not split", func(t *testing.T) {
someString := "some string"
out := splitString(someString, " ", 50)
out := SplitString(someString, " ", 50)
require.Equal(t, 1, len(out))
// prefix was added
assert.Equal(t, len(someString)+2, len(out[0]))
Expand Down
92 changes: 92 additions & 0 deletions pkg/cert/pubkey.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package cert

import (
"encoding/asn1"
"fmt"
"github.com/pete911/certinfo/pkg/cert/format"
"math/big"
"strings"
)

type RSAPublicKeyInfo struct {
Modulus *big.Int
PublicExponent *big.Int
}

// RSAPublicKey ::= SEQUENCE {
// modulus INTEGER, -- n
// publicExponent INTEGER } -- e

func toRSAPublicKeyInfo(in []byte) (RSAPublicKeyInfo, error) {
var pk RSAPublicKeyInfo
if _, err := asn1.Unmarshal(in, &pk); err != nil {
return RSAPublicKeyInfo{}, err
}
return pk, nil
}

func (p RSAPublicKeyInfo) String() string {
lines := []string{
"Public Key Algorithm: RSA",
fmt.Sprintf(" Public Key: (%d bit)", p.Modulus.BitLen()),
" Modulus",
}
lines = append(lines, format.SplitString(format.HexArray(p.Modulus.Bytes()), " ", 45)...)
lines = append(lines, fmt.Sprintf(" Exponent: %s", p.PublicExponent))
return strings.Join(lines, "\n")
}

// SubjectPublicKeyInfo ::= SEQUENCE {
// algorithm AlgorithmIdentifier,
// subjectPublicKey BIT STRING }

// AlgorithmIdentifier ::= SEQUENCE {
// algorithm OBJECT IDENTIFIER,
// parameters ANY DEFINED BY algorithm OPTIONAL }

func ToSubjectPublicKeyInfo(in []byte) (fmt.Stringer, error) {
var out struct {
Algorithm struct {
Algorithm asn1.ObjectIdentifier
Parameters asn1.RawValue `asn1:"optional"`
}
SubjectPublicKey asn1.BitString
}
if _, err := asn1.Unmarshal(in, &out); err != nil {
return nil, err
}

algorithmOid := out.Algorithm.Algorithm.String()
var algorithm string
algorithm, ok := publicKeyAlgorithmOIDs[algorithmOid]
if !ok {
return nil, fmt.Errorf("unknown public key algorithm oid: %s", algorithm)
}

// RSA, DSA, ECDSA, X25519, Ed25519
// https://datatracker.ietf.org/doc/html/rfc3279
if algorithm == "RSA" {
return toRSAPublicKeyInfo(out.SubjectPublicKey.Bytes)
}
if algorithm == "DSA" {
// TODO
}
if algorithm == "ECDSA" {
// TODO
}
if algorithm == "X25519" {
// TODO
}
if algorithm == "Ed25519" {
// TODO
}
return nil, nil
}

var publicKeyAlgorithmOIDs = map[string]string{
"1.2.840.113549.1.1.1": "RSA",
"1.2.840.10040.4.1": "DSA",
"1.2.840.10045.2.1": "ECDSA",
"1.3.101.110": "X25519",
"1.3.101.112": "Ed25519",
}
Loading