From 83be18a24328b6123c7105c72828364ef096503c Mon Sep 17 00:00:00 2001 From: "peter.reisinger" Date: Mon, 7 Jul 2025 15:53:10 +0100 Subject: [PATCH 1/3] initial rsa public key implementation --- flag.go | 3 + google.com.pem | 78 +++++++++++++++++ google.com.pem2 | 29 +++++++ main.go | 2 +- pkg/cert/cert.go | 34 ++++---- pkg/cert/extensions.go | 5 +- pkg/cert/format/format.go | 43 +++++++++ pkg/{print => cert/format}/location_test.go | 8 +- pkg/cert/pubkey.go | 96 +++++++++++++++++++++ pkg/print/location.go | 47 +++------- 10 files changed, 287 insertions(+), 58 deletions(-) create mode 100644 google.com.pem create mode 100644 google.com.pem2 create mode 100644 pkg/cert/format/format.go rename pkg/{print => cert/format}/location_test.go (81%) create mode 100644 pkg/cert/pubkey.go diff --git a/flag.go b/flag.go index 8e57475..21f4995 100644 --- a/flag.go +++ b/flag.go @@ -19,6 +19,7 @@ type Flags struct { Insecure bool Chains bool Extensions bool + PublicKey bool Signature bool Pem bool PemOnly bool @@ -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), diff --git a/google.com.pem b/google.com.pem new file mode 100644 index 0000000..a7ffa4f --- /dev/null +++ b/google.com.pem @@ -0,0 +1,78 @@ +-----BEGIN CERTIFICATE----- +MIIOLTCCDRWgAwIBAgIQCwNj/SgfHZUK8/Le2Ywq1DANBgkqhkiG9w0BAQsFADA7 +MQswCQYDVQQGEwJVUzEeMBwGA1UEChMVR29vZ2xlIFRydXN0IFNlcnZpY2VzMQww +CgYDVQQDEwNXUjIwHhcNMjUwNjE3MjAwMTQ4WhcNMjUwOTA5MjAwMTQ3WjAXMRUw +EwYDVQQDDAwqLmdvb2dsZS5jb20wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAT+ +zM65kcv803D/uB8HxyQW0RFWUAwh4X2VymfpxL5RJXUXk0Mq7IWkhDsbWMQTOJrB +uLcnIzU85YPIswCHhtyao4IMGjCCDBYwDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQM +MAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFFIHsrTWV0z6O7aI +5YaBRO9a9knzMB8GA1UdIwQYMBaAFN4bHu15FdQ+NyTDIbvsNDltQrIwMFgGCCsG +AQUFBwEBBEwwSjAhBggrBgEFBQcwAYYVaHR0cDovL28ucGtpLmdvb2cvd3IyMCUG +CCsGAQUFBzAChhlodHRwOi8vaS5wa2kuZ29vZy93cjIuY3J0MIIJ8AYDVR0RBIIJ +5zCCCeOCDCouZ29vZ2xlLmNvbYIWKi5hcHBlbmdpbmUuZ29vZ2xlLmNvbYIJKi5i +ZG4uZGV2ghUqLm9yaWdpbi10ZXN0LmJkbi5kZXaCEiouY2xvdWQuZ29vZ2xlLmNv +bYIYKi5jcm93ZHNvdXJjZS5nb29nbGUuY29tghgqLmRhdGFjb21wdXRlLmdvb2ds +ZS5jb22CCyouZ29vZ2xlLmNhggsqLmdvb2dsZS5jbIIOKi5nb29nbGUuY28uaW6C +DiouZ29vZ2xlLmNvLmpwgg4qLmdvb2dsZS5jby51a4IPKi5nb29nbGUuY29tLmFy +gg8qLmdvb2dsZS5jb20uYXWCDyouZ29vZ2xlLmNvbS5icoIPKi5nb29nbGUuY29t +LmNvgg8qLmdvb2dsZS5jb20ubXiCDyouZ29vZ2xlLmNvbS50coIPKi5nb29nbGUu +Y29tLnZuggsqLmdvb2dsZS5kZYILKi5nb29nbGUuZXOCCyouZ29vZ2xlLmZyggsq +Lmdvb2dsZS5odYILKi5nb29nbGUuaXSCCyouZ29vZ2xlLm5sggsqLmdvb2dsZS5w +bIILKi5nb29nbGUucHSCDyouZ29vZ2xlYXBpcy5jboIRKi5nb29nbGV2aWRlby5j +b22CDCouZ3N0YXRpYy5jboIQKi5nc3RhdGljLWNuLmNvbYIPZ29vZ2xlY25hcHBz +LmNughEqLmdvb2dsZWNuYXBwcy5jboIRZ29vZ2xlYXBwcy1jbi5jb22CEyouZ29v +Z2xlYXBwcy1jbi5jb22CDGdrZWNuYXBwcy5jboIOKi5na2VjbmFwcHMuY26CEmdv +b2dsZWRvd25sb2Fkcy5jboIUKi5nb29nbGVkb3dubG9hZHMuY26CEHJlY2FwdGNo +YS5uZXQuY26CEioucmVjYXB0Y2hhLm5ldC5jboIQcmVjYXB0Y2hhLWNuLm5ldIIS +Ki5yZWNhcHRjaGEtY24ubmV0ggt3aWRldmluZS5jboINKi53aWRldmluZS5jboIR +YW1wcHJvamVjdC5vcmcuY26CEyouYW1wcHJvamVjdC5vcmcuY26CEWFtcHByb2pl +Y3QubmV0LmNughMqLmFtcHByb2plY3QubmV0LmNughdnb29nbGUtYW5hbHl0aWNz +LWNuLmNvbYIZKi5nb29nbGUtYW5hbHl0aWNzLWNuLmNvbYIXZ29vZ2xlYWRzZXJ2 +aWNlcy1jbi5jb22CGSouZ29vZ2xlYWRzZXJ2aWNlcy1jbi5jb22CEWdvb2dsZXZh +ZHMtY24uY29tghMqLmdvb2dsZXZhZHMtY24uY29tghFnb29nbGVhcGlzLWNuLmNv +bYITKi5nb29nbGVhcGlzLWNuLmNvbYIVZ29vZ2xlb3B0aW1pemUtY24uY29tghcq +Lmdvb2dsZW9wdGltaXplLWNuLmNvbYISZG91YmxlY2xpY2stY24ubmV0ghQqLmRv +dWJsZWNsaWNrLWNuLm5ldIIYKi5mbHMuZG91YmxlY2xpY2stY24ubmV0ghYqLmcu +ZG91YmxlY2xpY2stY24ubmV0gg5kb3VibGVjbGljay5jboIQKi5kb3VibGVjbGlj +ay5jboIUKi5mbHMuZG91YmxlY2xpY2suY26CEiouZy5kb3VibGVjbGljay5jboIR +ZGFydHNlYXJjaC1jbi5uZXSCEyouZGFydHNlYXJjaC1jbi5uZXSCHWdvb2dsZXRy +YXZlbGFkc2VydmljZXMtY24uY29tgh8qLmdvb2dsZXRyYXZlbGFkc2VydmljZXMt +Y24uY29tghhnb29nbGV0YWdzZXJ2aWNlcy1jbi5jb22CGiouZ29vZ2xldGFnc2Vy +dmljZXMtY24uY29tghdnb29nbGV0YWdtYW5hZ2VyLWNuLmNvbYIZKi5nb29nbGV0 +YWdtYW5hZ2VyLWNuLmNvbYIYZ29vZ2xlc3luZGljYXRpb24tY24uY29tghoqLmdv +b2dsZXN5bmRpY2F0aW9uLWNuLmNvbYIkKi5zYWZlZnJhbWUuZ29vZ2xlc3luZGlj +YXRpb24tY24uY29tghZhcHAtbWVhc3VyZW1lbnQtY24uY29tghgqLmFwcC1tZWFz +dXJlbWVudC1jbi5jb22CC2d2dDEtY24uY29tgg0qLmd2dDEtY24uY29tggtndnQy +LWNuLmNvbYINKi5ndnQyLWNuLmNvbYILMm1kbi1jbi5uZXSCDSouMm1kbi1jbi5u +ZXSCFGdvb2dsZWZsaWdodHMtY24ubmV0ghYqLmdvb2dsZWZsaWdodHMtY24ubmV0 +ggxhZG1vYi1jbi5jb22CDiouYWRtb2ItY24uY29tghRnb29nbGVzYW5kYm94LWNu +LmNvbYIWKi5nb29nbGVzYW5kYm94LWNuLmNvbYIeKi5zYWZlbnVwLmdvb2dsZXNh +bmRib3gtY24uY29tgg0qLmdzdGF0aWMuY29tghQqLm1ldHJpYy5nc3RhdGljLmNv +bYIKKi5ndnQxLmNvbYIRKi5nY3BjZG4uZ3Z0MS5jb22CCiouZ3Z0Mi5jb22CDiou +Z2NwLmd2dDIuY29tghAqLnVybC5nb29nbGUuY29tghYqLnlvdXR1YmUtbm9jb29r +aWUuY29tggsqLnl0aW1nLmNvbYIKYWkuYW5kcm9pZIILYW5kcm9pZC5jb22CDSou +YW5kcm9pZC5jb22CEyouZmxhc2guYW5kcm9pZC5jb22CBGcuY26CBiouZy5jboIE +Zy5jb4IGKi5nLmNvggZnb28uZ2yCCnd3dy5nb28uZ2yCFGdvb2dsZS1hbmFseXRp +Y3MuY29tghYqLmdvb2dsZS1hbmFseXRpY3MuY29tggpnb29nbGUuY29tghJnb29n +bGVjb21tZXJjZS5jb22CFCouZ29vZ2xlY29tbWVyY2UuY29tgghnZ3BodC5jboIK +Ki5nZ3BodC5jboIKdXJjaGluLmNvbYIMKi51cmNoaW4uY29tggh5b3V0dS5iZYIL +eW91dHViZS5jb22CDSoueW91dHViZS5jb22CEW11c2ljLnlvdXR1YmUuY29tghMq +Lm11c2ljLnlvdXR1YmUuY29tghR5b3V0dWJlZWR1Y2F0aW9uLmNvbYIWKi55b3V0 +dWJlZWR1Y2F0aW9uLmNvbYIPeW91dHViZWtpZHMuY29tghEqLnlvdXR1YmVraWRz +LmNvbYIFeXQuYmWCByoueXQuYmWCGmFuZHJvaWQuY2xpZW50cy5nb29nbGUuY29t +ghMqLmFuZHJvaWQuZ29vZ2xlLmNughIqLmNocm9tZS5nb29nbGUuY26CFiouZGV2 +ZWxvcGVycy5nb29nbGUuY26CFSouYWlzdHVkaW8uZ29vZ2xlLmNvbTATBgNVHSAE +DDAKMAgGBmeBDAECATA2BgNVHR8ELzAtMCugKaAnhiVodHRwOi8vYy5wa2kuZ29v +Zy93cjIvb0JGWVlhaHpnVkkuY3JsMIIBBAYKKwYBBAHWeQIEAgSB9QSB8gDwAHUA +3dzKNJXX4RYF55Uy+sef+D0cUN/bADoUEnYKLKy7yCoAAAGXf7LDuAAABAMARjBE +AiAeY2Yqs3emxyQ4iGT/It92t8CjniRfT/hvwyknlyrLjgIgGf8v4wTjBGxVY+uM ++jeFnDWQjPPwCNecHI1ZSOe9/YEAdwDM+w9qhXEJZf6Vm1PO6bJ8IumFXA2Xjbap +flTA/kwNsAAAAZd/ssO/AAAEAwBIMEYCIQDBzji0dlASNv8zfRMTf6tpqpnJrDLb +OlBcSLwbz5a8oAIhAK4xxZncs6tweBosh/NrQV8tleYIGyiWrTcnWNW/L/gvMA0G +CSqGSIb3DQEBCwUAA4IBAQACp7ILmldzA5MEiUDgzk0rFAiFJibDs5Ok24pqoAFI +dAYNh9Pt4O5TSDtufVO6+wFdiz1Is4cDSfsZiewOBgJaA3QrsMUp0EXydkSIcXyt +cAJXu9Y11+0Y8Qb7Xr9rO7tP0JJsVVv8TNDiQly1GxgaYFdhcj5WJ0AzF2d7wnir +RYTrOCo5+Gs7CJTMWi71bvWStYQR0v3EjW/vP9+X7ttPMERB6oz6Hha+C4sII/Fb +nCAZ7dKWwnR5xxWE3zbqb5exXobNUdtvW5vfQ1RQyn60pXnsi1+7zl3ZTqJuR4wp +cqHPR4H37H1cWKLnI445f8aYXOEwcGFd5ALroWZo2FvU +-----END CERTIFICATE----- diff --git a/google.com.pem2 b/google.com.pem2 new file mode 100644 index 0000000..e374984 --- /dev/null +++ b/google.com.pem2 @@ -0,0 +1,29 @@ +-----BEGIN CERTIFICATE----- +MIIFCzCCAvOgAwIBAgIQf/AFoHxM3tEArZ1mpRB7mDANBgkqhkiG9w0BAQsFADBH +MQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExM +QzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwHhcNMjMxMjEzMDkwMDAwWhcNMjkwMjIw +MTQwMDAwWjA7MQswCQYDVQQGEwJVUzEeMBwGA1UEChMVR29vZ2xlIFRydXN0IFNl +cnZpY2VzMQwwCgYDVQQDEwNXUjIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQCp/5x/RR5wqFOfytnlDd5GV1d9vI+aWqxG8YSau5HbyfsvAfuSCQAWXqAc ++MGr+XgvSszYhaLYWTwO0xj7sfUkDSbutltkdnwUxy96zqhMt/TZCPzfhyM1IKji +aeKMTj+xWfpgoh6zySBTGYLKNlNtYE3pAJH8do1cCA8Kwtzxc2vFE24KT3rC8gIc +LrRjg9ox9i11MLL7q8Ju26nADrn5Z9TDJVd06wW06Y613ijNzHoU5HEDy01hLmFX +xRmpC5iEGuh5KdmyjS//V2pm4M6rlagplmNwEmceOuHbsCFx13ye/aoXbv4r+zgX +FNFmp6+atXDMyGOBOozAKql2N87jAgMBAAGjgf4wgfswDgYDVR0PAQH/BAQDAgGG +MB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjASBgNVHRMBAf8ECDAGAQH/ +AgEAMB0GA1UdDgQWBBTeGx7teRXUPjckwyG77DQ5bUKyMDAfBgNVHSMEGDAWgBTk +rysmcRorSCeFL1JmLO/wiRNxPjA0BggrBgEFBQcBAQQoMCYwJAYIKwYBBQUHMAKG +GGh0dHA6Ly9pLnBraS5nb29nL3IxLmNydDArBgNVHR8EJDAiMCCgHqAchhpodHRw +Oi8vYy5wa2kuZ29vZy9yL3IxLmNybDATBgNVHSAEDDAKMAgGBmeBDAECATANBgkq +hkiG9w0BAQsFAAOCAgEARXWL5R87RBOWGqtY8TXJbz3S0DNKhjO6V1FP7sQ02hYS +TL8Tnw3UVOlIecAwPJQl8hr0ujKUtjNyC4XuCRElNJThb0Lbgpt7fyqaqf9/qdLe +SiDLs/sDA7j4BwXaWZIvGEaYzq9yviQmsR4ATb0IrZNBRAq7x9UBhb+TV+PfdBJT +DhEl05vc3ssnbrPCuTNiOcLgNeFbpwkuGcuRKnZc8d/KI4RApW//mkHgte8y0YWu +ryUJ8GLFbsLIbjL9uNrizkqRSvOFVU6xddZIMy9vhNkSXJ/UcZhjJY1pXAprffJB +vei7j+Qi151lRehMCofa6WBmiA4fx+FOVsV2/7R6V2nyAiIJJkEd2nSi5SnzxJrl +Xdaqev3htytmOPvoKWa676ATL/hzfvDaQBEcXd2Ppvy+275W+DKcH0FBbX62xevG +iza3F4ydzxl6NJ8hk8R+dDXSqv1MbRT1ybB5W0k8878XSOjvmiYTDIfyc9acxVJr +Y/cykHipa+te1pOhv7wYPYtZ9orGBV5SGOJm4NrB3K1aJar0RfzxC3ikr7Dyc6Qw +qDTBU39CluVIQeuQRgwG3MuSxl7zRERDRilGoKb8uY45JzmxWuKxrfwT/478JuHU +/oTxUFqOl2stKnn7QGTq8z29W+GgBLCXSBxC9epaHM0myFH/FJlniXJfHeytWt0= +-----END CERTIFICATE----- diff --git a/main.go b/main.go index 3dc02f4..ad732a2 100644 --- a/main.go +++ b/main.go @@ -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) { diff --git a/pkg/cert/cert.go b/pkg/cert/cert.go index fe33f11..7ebf2ad 100644 --- a/pkg/cert/cert.go +++ b/pkg/cert/cert.go @@ -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" @@ -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() } @@ -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 "" } @@ -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 { @@ -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])) -} diff --git a/pkg/cert/extensions.go b/pkg/cert/extensions.go index 0971bde..ac87cb6 100644 --- a/pkg/cert/extensions.go +++ b/pkg/cert/extensions.go @@ -4,6 +4,7 @@ import ( "crypto/x509/pkix" "encoding/asn1" "fmt" + "github.com/pete911/certinfo/pkg/cert/format" "strings" ) @@ -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)) @@ -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 { diff --git a/pkg/cert/format/format.go b/pkg/cert/format/format.go new file mode 100644 index 0000000..c7737a1 --- /dev/null +++ b/pkg/cert/format/format.go @@ -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 +} diff --git a/pkg/print/location_test.go b/pkg/cert/format/location_test.go similarity index 81% rename from pkg/print/location_test.go rename to pkg/cert/format/location_test.go index e388fe8..3ec2435 100644 --- a/pkg/print/location_test.go +++ b/pkg/cert/format/location_test.go @@ -1,4 +1,4 @@ -package print +package format import ( "github.com/stretchr/testify/assert" @@ -6,10 +6,10 @@ import ( "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) @@ -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])) diff --git a/pkg/cert/pubkey.go b/pkg/cert/pubkey.go new file mode 100644 index 0000000..847349a --- /dev/null +++ b/pkg/cert/pubkey.go @@ -0,0 +1,96 @@ +package cert + +import ( + "encoding/asn1" + "fmt" + "github.com/pete911/certinfo/pkg/cert/format" + "math/big" + "strings" +) + +type SubjectPublicKeyInfo interface { + String() string +} + +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) (SubjectPublicKeyInfo, 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", +} diff --git a/pkg/print/location.go b/pkg/print/location.go index 5f3b50b..3d068d2 100644 --- a/pkg/print/location.go +++ b/pkg/print/location.go @@ -3,12 +3,12 @@ package print import ( "fmt" "github.com/pete911/certinfo/pkg/cert" + "github.com/pete911/certinfo/pkg/cert/format" "log/slog" "strings" - "time" ) -func Locations(certificateLocations []cert.CertificateLocation, printChains, printPem, printExtensions, printSignature bool) { +func Locations(certificateLocations []cert.CertificateLocation, printChains, printPem, printExtensions, printPublicKey, printSignature bool) { for _, certificateLocation := range certificateLocations { if certificateLocation.Error != nil { @@ -19,7 +19,7 @@ func Locations(certificateLocations []cert.CertificateLocation, printChains, pri } fmt.Printf("--- [%s] ---\n", certificateLocation.Name()) - printCertificates(certificateLocation.Certificates, printPem, printExtensions, printSignature) + printCertificates(certificateLocation.Certificates, printPem, printExtensions, printPublicKey, printSignature) if printChains { chains, err := certificateLocation.Chains() @@ -36,16 +36,16 @@ func Locations(certificateLocations []cert.CertificateLocation, printChains, pri } for i, chain := range chains { fmt.Printf(" -- [chain %d] -- \n", i+1) - printCertificates(chain, printPem, printExtensions, printSignature) + printCertificates(chain, printPem, printExtensions, printPublicKey, printSignature) } } } } -func printCertificates(certs cert.Certificates, printPem, printExtensions, printSignature bool) { +func printCertificates(certs cert.Certificates, printPem, printExtensions, printPublicKey, printSignature bool) { for _, certificate := range certs { - printCertificate(certificate, printExtensions, printSignature) + printCertificate(certificate, printExtensions, printPublicKey, printSignature) fmt.Println() if printPem { fmt.Println(string(certificate.ToPEM())) @@ -53,7 +53,7 @@ func printCertificates(certs cert.Certificates, printPem, printExtensions, print } } -func printCertificate(certificate cert.Certificate, printExtensions, printSignature bool) { +func printCertificate(certificate cert.Certificate, printExtensions, printPublicKey, printSignature bool) { if certificate.Error() != nil { slog.Error(certificate.Error().Error()) @@ -67,8 +67,8 @@ func printCertificate(certificate cert.Certificate, printExtensions, printSignat fmt.Printf("Type: %s\n", certificate.Type()) fmt.Printf("Issuer: %s\n", certificate.Issuer()) fmt.Println("Validity") - fmt.Printf(" Not Before: %s\n", validityFormat(certificate.NotBefore())) - fmt.Printf(" Not After : %s\n", validityFormat(certificate.NotAfter())) + fmt.Printf(" Not Before: %s\n", format.Validity(certificate.NotBefore())) + fmt.Printf(" Not After : %s\n", format.Validity(certificate.NotAfter())) fmt.Printf("Subject: %s\n", certificate.SubjectString()) fmt.Printf("DNS Names: %s\n", strings.Join(certificate.DNSNames(), ", ")) fmt.Printf("IP Addresses: %s\n", strings.Join(certificate.IPAddresses(), ", ")) @@ -94,34 +94,15 @@ func printCertificate(certificate cert.Certificate, printExtensions, printSignat } } + if printPublicKey { + fmt.Println(certificate.SubjectPublicKeyInfo()) + } + if printSignature { fmt.Printf("Signature Algorithm: %s\n", certificate.SignatureAlgorithm()) fmt.Println("Signature Value") - for _, line := range splitString(certificate.Signature(), " ", 54) { + for _, line := range format.SplitString(certificate.Signature(), " ", 54) { fmt.Println(line) } } } - -func validityFormat(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 -} From ec1445ab76df50b7b4d5521469f3b706952d1172 Mon Sep 17 00:00:00 2001 From: "peter.reisinger" Date: Mon, 7 Jul 2025 15:53:59 +0100 Subject: [PATCH 2/3] remove test files --- google.com.pem | 78 ------------------------------------------------- google.com.pem2 | 29 ------------------ 2 files changed, 107 deletions(-) delete mode 100644 google.com.pem delete mode 100644 google.com.pem2 diff --git a/google.com.pem b/google.com.pem deleted file mode 100644 index a7ffa4f..0000000 --- a/google.com.pem +++ /dev/null @@ -1,78 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIOLTCCDRWgAwIBAgIQCwNj/SgfHZUK8/Le2Ywq1DANBgkqhkiG9w0BAQsFADA7 -MQswCQYDVQQGEwJVUzEeMBwGA1UEChMVR29vZ2xlIFRydXN0IFNlcnZpY2VzMQww -CgYDVQQDEwNXUjIwHhcNMjUwNjE3MjAwMTQ4WhcNMjUwOTA5MjAwMTQ3WjAXMRUw -EwYDVQQDDAwqLmdvb2dsZS5jb20wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAT+ -zM65kcv803D/uB8HxyQW0RFWUAwh4X2VymfpxL5RJXUXk0Mq7IWkhDsbWMQTOJrB -uLcnIzU85YPIswCHhtyao4IMGjCCDBYwDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQM -MAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFFIHsrTWV0z6O7aI -5YaBRO9a9knzMB8GA1UdIwQYMBaAFN4bHu15FdQ+NyTDIbvsNDltQrIwMFgGCCsG -AQUFBwEBBEwwSjAhBggrBgEFBQcwAYYVaHR0cDovL28ucGtpLmdvb2cvd3IyMCUG -CCsGAQUFBzAChhlodHRwOi8vaS5wa2kuZ29vZy93cjIuY3J0MIIJ8AYDVR0RBIIJ -5zCCCeOCDCouZ29vZ2xlLmNvbYIWKi5hcHBlbmdpbmUuZ29vZ2xlLmNvbYIJKi5i -ZG4uZGV2ghUqLm9yaWdpbi10ZXN0LmJkbi5kZXaCEiouY2xvdWQuZ29vZ2xlLmNv -bYIYKi5jcm93ZHNvdXJjZS5nb29nbGUuY29tghgqLmRhdGFjb21wdXRlLmdvb2ds -ZS5jb22CCyouZ29vZ2xlLmNhggsqLmdvb2dsZS5jbIIOKi5nb29nbGUuY28uaW6C -DiouZ29vZ2xlLmNvLmpwgg4qLmdvb2dsZS5jby51a4IPKi5nb29nbGUuY29tLmFy -gg8qLmdvb2dsZS5jb20uYXWCDyouZ29vZ2xlLmNvbS5icoIPKi5nb29nbGUuY29t -LmNvgg8qLmdvb2dsZS5jb20ubXiCDyouZ29vZ2xlLmNvbS50coIPKi5nb29nbGUu -Y29tLnZuggsqLmdvb2dsZS5kZYILKi5nb29nbGUuZXOCCyouZ29vZ2xlLmZyggsq -Lmdvb2dsZS5odYILKi5nb29nbGUuaXSCCyouZ29vZ2xlLm5sggsqLmdvb2dsZS5w -bIILKi5nb29nbGUucHSCDyouZ29vZ2xlYXBpcy5jboIRKi5nb29nbGV2aWRlby5j -b22CDCouZ3N0YXRpYy5jboIQKi5nc3RhdGljLWNuLmNvbYIPZ29vZ2xlY25hcHBz -LmNughEqLmdvb2dsZWNuYXBwcy5jboIRZ29vZ2xlYXBwcy1jbi5jb22CEyouZ29v -Z2xlYXBwcy1jbi5jb22CDGdrZWNuYXBwcy5jboIOKi5na2VjbmFwcHMuY26CEmdv -b2dsZWRvd25sb2Fkcy5jboIUKi5nb29nbGVkb3dubG9hZHMuY26CEHJlY2FwdGNo -YS5uZXQuY26CEioucmVjYXB0Y2hhLm5ldC5jboIQcmVjYXB0Y2hhLWNuLm5ldIIS -Ki5yZWNhcHRjaGEtY24ubmV0ggt3aWRldmluZS5jboINKi53aWRldmluZS5jboIR -YW1wcHJvamVjdC5vcmcuY26CEyouYW1wcHJvamVjdC5vcmcuY26CEWFtcHByb2pl -Y3QubmV0LmNughMqLmFtcHByb2plY3QubmV0LmNughdnb29nbGUtYW5hbHl0aWNz -LWNuLmNvbYIZKi5nb29nbGUtYW5hbHl0aWNzLWNuLmNvbYIXZ29vZ2xlYWRzZXJ2 -aWNlcy1jbi5jb22CGSouZ29vZ2xlYWRzZXJ2aWNlcy1jbi5jb22CEWdvb2dsZXZh -ZHMtY24uY29tghMqLmdvb2dsZXZhZHMtY24uY29tghFnb29nbGVhcGlzLWNuLmNv -bYITKi5nb29nbGVhcGlzLWNuLmNvbYIVZ29vZ2xlb3B0aW1pemUtY24uY29tghcq -Lmdvb2dsZW9wdGltaXplLWNuLmNvbYISZG91YmxlY2xpY2stY24ubmV0ghQqLmRv -dWJsZWNsaWNrLWNuLm5ldIIYKi5mbHMuZG91YmxlY2xpY2stY24ubmV0ghYqLmcu -ZG91YmxlY2xpY2stY24ubmV0gg5kb3VibGVjbGljay5jboIQKi5kb3VibGVjbGlj -ay5jboIUKi5mbHMuZG91YmxlY2xpY2suY26CEiouZy5kb3VibGVjbGljay5jboIR -ZGFydHNlYXJjaC1jbi5uZXSCEyouZGFydHNlYXJjaC1jbi5uZXSCHWdvb2dsZXRy -YXZlbGFkc2VydmljZXMtY24uY29tgh8qLmdvb2dsZXRyYXZlbGFkc2VydmljZXMt -Y24uY29tghhnb29nbGV0YWdzZXJ2aWNlcy1jbi5jb22CGiouZ29vZ2xldGFnc2Vy -dmljZXMtY24uY29tghdnb29nbGV0YWdtYW5hZ2VyLWNuLmNvbYIZKi5nb29nbGV0 -YWdtYW5hZ2VyLWNuLmNvbYIYZ29vZ2xlc3luZGljYXRpb24tY24uY29tghoqLmdv -b2dsZXN5bmRpY2F0aW9uLWNuLmNvbYIkKi5zYWZlZnJhbWUuZ29vZ2xlc3luZGlj -YXRpb24tY24uY29tghZhcHAtbWVhc3VyZW1lbnQtY24uY29tghgqLmFwcC1tZWFz -dXJlbWVudC1jbi5jb22CC2d2dDEtY24uY29tgg0qLmd2dDEtY24uY29tggtndnQy -LWNuLmNvbYINKi5ndnQyLWNuLmNvbYILMm1kbi1jbi5uZXSCDSouMm1kbi1jbi5u -ZXSCFGdvb2dsZWZsaWdodHMtY24ubmV0ghYqLmdvb2dsZWZsaWdodHMtY24ubmV0 -ggxhZG1vYi1jbi5jb22CDiouYWRtb2ItY24uY29tghRnb29nbGVzYW5kYm94LWNu -LmNvbYIWKi5nb29nbGVzYW5kYm94LWNuLmNvbYIeKi5zYWZlbnVwLmdvb2dsZXNh -bmRib3gtY24uY29tgg0qLmdzdGF0aWMuY29tghQqLm1ldHJpYy5nc3RhdGljLmNv -bYIKKi5ndnQxLmNvbYIRKi5nY3BjZG4uZ3Z0MS5jb22CCiouZ3Z0Mi5jb22CDiou -Z2NwLmd2dDIuY29tghAqLnVybC5nb29nbGUuY29tghYqLnlvdXR1YmUtbm9jb29r -aWUuY29tggsqLnl0aW1nLmNvbYIKYWkuYW5kcm9pZIILYW5kcm9pZC5jb22CDSou -YW5kcm9pZC5jb22CEyouZmxhc2guYW5kcm9pZC5jb22CBGcuY26CBiouZy5jboIE -Zy5jb4IGKi5nLmNvggZnb28uZ2yCCnd3dy5nb28uZ2yCFGdvb2dsZS1hbmFseXRp -Y3MuY29tghYqLmdvb2dsZS1hbmFseXRpY3MuY29tggpnb29nbGUuY29tghJnb29n -bGVjb21tZXJjZS5jb22CFCouZ29vZ2xlY29tbWVyY2UuY29tgghnZ3BodC5jboIK -Ki5nZ3BodC5jboIKdXJjaGluLmNvbYIMKi51cmNoaW4uY29tggh5b3V0dS5iZYIL -eW91dHViZS5jb22CDSoueW91dHViZS5jb22CEW11c2ljLnlvdXR1YmUuY29tghMq -Lm11c2ljLnlvdXR1YmUuY29tghR5b3V0dWJlZWR1Y2F0aW9uLmNvbYIWKi55b3V0 -dWJlZWR1Y2F0aW9uLmNvbYIPeW91dHViZWtpZHMuY29tghEqLnlvdXR1YmVraWRz -LmNvbYIFeXQuYmWCByoueXQuYmWCGmFuZHJvaWQuY2xpZW50cy5nb29nbGUuY29t -ghMqLmFuZHJvaWQuZ29vZ2xlLmNughIqLmNocm9tZS5nb29nbGUuY26CFiouZGV2 -ZWxvcGVycy5nb29nbGUuY26CFSouYWlzdHVkaW8uZ29vZ2xlLmNvbTATBgNVHSAE -DDAKMAgGBmeBDAECATA2BgNVHR8ELzAtMCugKaAnhiVodHRwOi8vYy5wa2kuZ29v -Zy93cjIvb0JGWVlhaHpnVkkuY3JsMIIBBAYKKwYBBAHWeQIEAgSB9QSB8gDwAHUA -3dzKNJXX4RYF55Uy+sef+D0cUN/bADoUEnYKLKy7yCoAAAGXf7LDuAAABAMARjBE -AiAeY2Yqs3emxyQ4iGT/It92t8CjniRfT/hvwyknlyrLjgIgGf8v4wTjBGxVY+uM -+jeFnDWQjPPwCNecHI1ZSOe9/YEAdwDM+w9qhXEJZf6Vm1PO6bJ8IumFXA2Xjbap -flTA/kwNsAAAAZd/ssO/AAAEAwBIMEYCIQDBzji0dlASNv8zfRMTf6tpqpnJrDLb -OlBcSLwbz5a8oAIhAK4xxZncs6tweBosh/NrQV8tleYIGyiWrTcnWNW/L/gvMA0G -CSqGSIb3DQEBCwUAA4IBAQACp7ILmldzA5MEiUDgzk0rFAiFJibDs5Ok24pqoAFI -dAYNh9Pt4O5TSDtufVO6+wFdiz1Is4cDSfsZiewOBgJaA3QrsMUp0EXydkSIcXyt -cAJXu9Y11+0Y8Qb7Xr9rO7tP0JJsVVv8TNDiQly1GxgaYFdhcj5WJ0AzF2d7wnir -RYTrOCo5+Gs7CJTMWi71bvWStYQR0v3EjW/vP9+X7ttPMERB6oz6Hha+C4sII/Fb -nCAZ7dKWwnR5xxWE3zbqb5exXobNUdtvW5vfQ1RQyn60pXnsi1+7zl3ZTqJuR4wp -cqHPR4H37H1cWKLnI445f8aYXOEwcGFd5ALroWZo2FvU ------END CERTIFICATE----- diff --git a/google.com.pem2 b/google.com.pem2 deleted file mode 100644 index e374984..0000000 --- a/google.com.pem2 +++ /dev/null @@ -1,29 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIFCzCCAvOgAwIBAgIQf/AFoHxM3tEArZ1mpRB7mDANBgkqhkiG9w0BAQsFADBH -MQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExM -QzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwHhcNMjMxMjEzMDkwMDAwWhcNMjkwMjIw -MTQwMDAwWjA7MQswCQYDVQQGEwJVUzEeMBwGA1UEChMVR29vZ2xlIFRydXN0IFNl -cnZpY2VzMQwwCgYDVQQDEwNXUjIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK -AoIBAQCp/5x/RR5wqFOfytnlDd5GV1d9vI+aWqxG8YSau5HbyfsvAfuSCQAWXqAc -+MGr+XgvSszYhaLYWTwO0xj7sfUkDSbutltkdnwUxy96zqhMt/TZCPzfhyM1IKji -aeKMTj+xWfpgoh6zySBTGYLKNlNtYE3pAJH8do1cCA8Kwtzxc2vFE24KT3rC8gIc -LrRjg9ox9i11MLL7q8Ju26nADrn5Z9TDJVd06wW06Y613ijNzHoU5HEDy01hLmFX -xRmpC5iEGuh5KdmyjS//V2pm4M6rlagplmNwEmceOuHbsCFx13ye/aoXbv4r+zgX -FNFmp6+atXDMyGOBOozAKql2N87jAgMBAAGjgf4wgfswDgYDVR0PAQH/BAQDAgGG -MB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjASBgNVHRMBAf8ECDAGAQH/ -AgEAMB0GA1UdDgQWBBTeGx7teRXUPjckwyG77DQ5bUKyMDAfBgNVHSMEGDAWgBTk -rysmcRorSCeFL1JmLO/wiRNxPjA0BggrBgEFBQcBAQQoMCYwJAYIKwYBBQUHMAKG -GGh0dHA6Ly9pLnBraS5nb29nL3IxLmNydDArBgNVHR8EJDAiMCCgHqAchhpodHRw -Oi8vYy5wa2kuZ29vZy9yL3IxLmNybDATBgNVHSAEDDAKMAgGBmeBDAECATANBgkq -hkiG9w0BAQsFAAOCAgEARXWL5R87RBOWGqtY8TXJbz3S0DNKhjO6V1FP7sQ02hYS -TL8Tnw3UVOlIecAwPJQl8hr0ujKUtjNyC4XuCRElNJThb0Lbgpt7fyqaqf9/qdLe -SiDLs/sDA7j4BwXaWZIvGEaYzq9yviQmsR4ATb0IrZNBRAq7x9UBhb+TV+PfdBJT -DhEl05vc3ssnbrPCuTNiOcLgNeFbpwkuGcuRKnZc8d/KI4RApW//mkHgte8y0YWu -ryUJ8GLFbsLIbjL9uNrizkqRSvOFVU6xddZIMy9vhNkSXJ/UcZhjJY1pXAprffJB -vei7j+Qi151lRehMCofa6WBmiA4fx+FOVsV2/7R6V2nyAiIJJkEd2nSi5SnzxJrl -Xdaqev3htytmOPvoKWa676ATL/hzfvDaQBEcXd2Ppvy+275W+DKcH0FBbX62xevG -iza3F4ydzxl6NJ8hk8R+dDXSqv1MbRT1ybB5W0k8878XSOjvmiYTDIfyc9acxVJr -Y/cykHipa+te1pOhv7wYPYtZ9orGBV5SGOJm4NrB3K1aJar0RfzxC3ikr7Dyc6Qw -qDTBU39CluVIQeuQRgwG3MuSxl7zRERDRilGoKb8uY45JzmxWuKxrfwT/478JuHU -/oTxUFqOl2stKnn7QGTq8z29W+GgBLCXSBxC9epaHM0myFH/FJlniXJfHeytWt0= ------END CERTIFICATE----- From 7ace4d57b34662965490e89a909d34735deca958 Mon Sep 17 00:00:00 2001 From: "peter.reisinger" Date: Mon, 7 Jul 2025 15:57:15 +0100 Subject: [PATCH 3/3] change public key return type to stringer --- pkg/cert/pubkey.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/pkg/cert/pubkey.go b/pkg/cert/pubkey.go index 847349a..a69e33b 100644 --- a/pkg/cert/pubkey.go +++ b/pkg/cert/pubkey.go @@ -8,10 +8,6 @@ import ( "strings" ) -type SubjectPublicKeyInfo interface { - String() string -} - type RSAPublicKeyInfo struct { Modulus *big.Int PublicExponent *big.Int @@ -48,7 +44,7 @@ func (p RSAPublicKeyInfo) String() string { // algorithm OBJECT IDENTIFIER, // parameters ANY DEFINED BY algorithm OPTIONAL } -func ToSubjectPublicKeyInfo(in []byte) (SubjectPublicKeyInfo, error) { +func ToSubjectPublicKeyInfo(in []byte) (fmt.Stringer, error) { var out struct { Algorithm struct { Algorithm asn1.ObjectIdentifier