diff --git a/lib/display.go b/lib/display.go index d011cb9..7fe9d95 100644 --- a/lib/display.go +++ b/lib/display.go @@ -26,15 +26,13 @@ import ( "text/template" "time" - "encoding/asn1" - "github.com/fatih/color" ) -var layout = ` +var verboseLayout = ` {{- define "PkixName" -}} {{- range .Names}} - {{ .Type | oidify }}: {{ .Value }} + {{ .Type | oidName }}: {{ .Value }} {{- end -}} {{end -}} @@ -66,6 +64,39 @@ Email Addresses:{{range .EmailAddresses}} Warnings:{{range .Warnings}} {{. | redify}}{{end}}{{end}}` +var layout = ` +{{- define "PkixName" -}} + {{- range $index, $element := .Names}} + {{- if $index}}, {{end}} + {{- $short := $element.Type | oidShort }} + {{- if $short -}} + {{ $short }}={{ .Value }} + {{- end}} + {{- end -}} +{{end -}} + +{{- if .Alias}}{{.Alias}} +{{end -}} +Not Before: {{.NotBefore | certStart}} +Not After : {{.NotAfter | certEnd}} +Subject: {{template "PkixName" .Subject.Name}} +Issuer: {{template "PkixName" .Issuer.Name}} +{{- if .NameConstraints}} +Name Constraints {{if .PermittedDNSDomains.Critical}}(critical){{end}}: {{range .NameConstraints.PermittedDNSDomains}} + {{.}}{{end}}{{end}} +{{- if .AltDNSNames}} +Alternate DNS Names:{{range .AltDNSNames}} + {{.}}{{end}}{{end}} +{{- if .AltIPAddresses}} +Alternate IP Addresses:{{range .AltIPAddresses}} + {{.}}{{end}}{{end}} +{{- if .EmailAddresses}} +Email Addresses:{{range .EmailAddresses}} + {{.}}{{end}}{{end}} +{{- if .Warnings}} +Warnings:{{range .Warnings}} + {{. | redify}}{{end}}{{end}}` + type certWithName struct { name string file string @@ -110,15 +141,15 @@ func EncodeX509ToObject(cert *x509.Certificate) interface{} { } // EncodeX509ToText encodes an X.509 certificate into human-readable text. -func EncodeX509ToText(cert *x509.Certificate) []byte { - return displayCert(createSimpleCertificate("", cert)) +func EncodeX509ToText(cert *x509.Certificate, verbose bool) []byte { + return displayCert(createSimpleCertificate("", cert), verbose) } // displayCert takes in a parsed certificate object // (for jceks certs, blank otherwise), and prints out relevant // information. Start and end dates are colored based on whether or not // the certificate is expired, not expired, or close to expiring. -func displayCert(cert simpleCertificate) []byte { +func displayCert(cert simpleCertificate, verbose bool) []byte { funcMap := template.FuncMap{ "certStart": certStart, "certEnd": certEnd, @@ -127,10 +158,16 @@ func displayCert(cert simpleCertificate) []byte { "hexify": hexify, "keyUsage": keyUsage, "extKeyUsage": extKeyUsage, - "oidify": oidify, + "oidName": oidName, + "oidShort": oidShort, } t := template.New("Cert template").Funcs(funcMap) - t, err := t.Parse(layout) + var err error + if verbose { + t, err = t.Parse(verboseLayout) + } else { + t, err = t.Parse(layout) + } if err != nil { // Should never happen panic(err) @@ -221,7 +258,3 @@ func certEnd(end time.Time) string { func redify(text string) string { return red.SprintfFunc()("%s", text) } - -func oidify(oid asn1.ObjectIdentifier) string { - return describeOid(oid).Name -} diff --git a/lib/oids.go b/lib/oids.go index 1e3d1f3..1512364 100644 --- a/lib/oids.go +++ b/lib/oids.go @@ -2,10 +2,11 @@ package lib import "encoding/asn1" -// OidDescription returns a human-readable name, a snake_case slug suitable as a json key, +// OidDescription returns a human-readable name, a short acronym from RFC1485, a snake_case slug suitable as a json key, // and a boolean describing whether multiple copies can appear on an X509 cert. type OidDescription struct { Name string + Short string Slug string Multiple bool } @@ -14,21 +15,29 @@ func describeOid(oid asn1.ObjectIdentifier) OidDescription { raw := oid.String() // Multiple should be true for any types that are []string in x509.pkix.Name. When in doubt, set it to true. names := map[string]OidDescription{ - "2.5.4.3": {"CommonName", "common_name", false}, - "2.5.4.5": {"EV Incorporation Registration Number", "ev_registration_number", false}, - "2.5.4.6": {"Country", "country", true}, - "2.5.4.7": {"Locality", "locality", true}, - "2.5.4.8": {"Province", "province", true}, - "2.5.4.10": {"Organization", "organization", true}, - "2.5.4.11": {"Organizational Unit", "organizational_unit", true}, - "2.5.4.15": {"Business Category", "business_category", true}, - "1.2.840.113549.1.9.1": {"Email Address", "email_address", true}, - "1.3.6.1.4.1.311.60.2.1.1": {"EV Incorporation Locality", "ev_locality", true}, - "1.3.6.1.4.1.311.60.2.1.2": {"EV Incorporation Province", "ev_province", true}, - "1.3.6.1.4.1.311.60.2.1.3": {"EV Incorporation Country", "ev_country", true}, + "2.5.4.3": {"CommonName", "CN", "common_name", false}, + "2.5.4.5": {"EV Incorporation Registration Number", "", "ev_registration_number", false}, + "2.5.4.6": {"Country", "C", "country", true}, + "2.5.4.7": {"Locality", "L", "locality", true}, + "2.5.4.8": {"Province", "ST", "province", true}, + "2.5.4.10": {"Organization", "O", "organization", true}, + "2.5.4.11": {"Organizational Unit", "OU", "organizational_unit", true}, + "2.5.4.15": {"Business Category", "", "business_category", true}, + "1.2.840.113549.1.9.1": {"Email Address", "", "email_address", true}, + "1.3.6.1.4.1.311.60.2.1.1": {"EV Incorporation Locality", "", "ev_locality", true}, + "1.3.6.1.4.1.311.60.2.1.2": {"EV Incorporation Province", "", "ev_province", true}, + "1.3.6.1.4.1.311.60.2.1.3": {"EV Incorporation Country", "", "ev_country", true}, } if description, ok := names[raw]; ok { return description } - return OidDescription{raw, raw, true} + return OidDescription{raw, "", raw, true} +} + +func oidShort(oid asn1.ObjectIdentifier) string { + return describeOid(oid).Short +} + +func oidName(oid asn1.ObjectIdentifier) string { + return describeOid(oid).Name } diff --git a/main.go b/main.go index 26b1816..5c904ab 100644 --- a/main.go +++ b/main.go @@ -33,7 +33,8 @@ import ( ) var ( - app = kingpin.New("certigo", "A command line certificate examination utility.") + app = kingpin.New("certigo", "A command line certificate examination utility.") + verbose = app.Flag("verbose", "Print verbose").Short('v').Bool() dump = app.Command("dump", "Display information about a certificate from a file/stdin.") dumpFiles = dump.Arg("file", "Certificate file to dump (or stdin if not specified).").ExistingFiles() @@ -91,7 +92,7 @@ func main() { } else { for i, cert := range result.Certificates { fmt.Fprintf(stdout, "** CERTIFICATE %d **\n", i+1) - fmt.Fprintf(stdout, "%s\n\n", lib.EncodeX509ToText(cert)) + fmt.Fprintf(stdout, "%s\n\n", lib.EncodeX509ToText(cert, *verbose)) } } } @@ -129,7 +130,7 @@ func main() { fmt.Fprintf(stdout, "%s\n\n", lib.EncodeTLSToText(result.TLSConnectionState)) for i, cert := range result.Certificates { fmt.Fprintf(stdout, "** CERTIFICATE %d **\n", i+1) - fmt.Fprintf(stdout, "%s\n\n", lib.EncodeX509ToText(cert)) + fmt.Fprintf(stdout, "%s\n\n", lib.EncodeX509ToText(cert, *verbose)) } printVerifyResult(stdout, *result.VerifyResult) } diff --git a/tests/dump-cert-chain-to-text.t b/tests/dump-cert-chain-to-text.t index d2704ef..23e6a7f 100644 --- a/tests/dump-cert-chain-to-text.t +++ b/tests/dump-cert-chain-to-text.t @@ -107,7 +107,7 @@ Set up test data. Dump a live cert chain (squareup-chain.crt) - $ certigo dump squareup-chain.crt + $ certigo --verbose dump squareup-chain.crt ** CERTIFICATE 1 ** Serial: 260680855742043049380997676879525498489 Not Before: 2016-07-15 20:15:52 +0000 UTC diff --git a/tests/dump-jceks-to-pem.t b/tests/dump-jceks-to-pem.t index 22f2bbb..3281772 100644 --- a/tests/dump-jceks-to-pem.t +++ b/tests/dump-jceks-to-pem.t @@ -51,7 +51,7 @@ Set up test data. Dump PEM blocks from a JCEKS keystore. - $ certigo dump --pem --password password example.jceks + $ certigo --verbose dump --pem --password password example.jceks -----BEGIN RSA PRIVATE KEY----- MIIEpQIBAAKCAQEAyjhKEojYzEPP81BXEFvPS1sPlHk1yhsDQ0qRo8ovIwSmdZVj 25zJq4yOg2fUXnrxx86gXSmbfvTRTxRM+3YwzEQciLiYUb84XxQX5WiHUnjZNvja diff --git a/tests/dump-leaf-to-not-verbose.t b/tests/dump-leaf-to-not-verbose.t new file mode 100644 index 0000000..7906bbc --- /dev/null +++ b/tests/dump-leaf-to-not-verbose.t @@ -0,0 +1,40 @@ +Set up test data. + + $ cat > example-leaf.crt < -----BEGIN CERTIFICATE----- + > MIIDfDCCAmSgAwIBAgIJANWAkzF7PA8/MA0GCSqGSIb3DQEBCwUAMFUxCzAJBgNV + > BAYTAlVTMQswCQYDVQQIEwJDQTEQMA4GA1UEChMHY2VydGlnbzEQMA4GA1UECxMH + > ZXhhbXBsZTEVMBMGA1UEAxMMZXhhbXBsZS1sZWFmMB4XDTE2MDYxMDIyMTQxMVoX + > DTIzMDQxNTIyMTQxMVowVTELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRAwDgYD + > VQQKEwdjZXJ0aWdvMRAwDgYDVQQLEwdleGFtcGxlMRUwEwYDVQQDEwxleGFtcGxl + > LWxlYWYwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC7stSvfQyGuHw3 + > v34fisqIdDXberrFoFk9ht/WdXgYzX2uLNKdsR/J5sbWSl8K/5djpzj31eIzqU69 + > w8v7SChM5x9bouDsABHz3kZucx5cSafEgJojysBkcrq3VY+aJanzbL+qErYX+lhR + > pPcZK6JMWIwar8Y3B2la4yWwieecw2/WfEVvG0M/DOYKnR8QHFsfl3US1dnBM84c + > zKPyt9r40gDk2XiH/lGts5a94rAGvbr8IMCtq0mA5aH3Fx3mDSi3+4MZwygCAHrF + > 5O5iSV9rEI+m2+7j2S+jHDUnvV+nqcpb9m6ENECnYX8FD2KcqlOjTmw8smDy09N2 + > Np6i464lAgMBAAGjTzBNMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATAs + > BgNVHREEJTAjhwR/AAABhxAAAAAAAAAAAAAAAAAAAAABgglsb2NhbGhvc3QwDQYJ + > KoZIhvcNAQELBQADggEBAGM4aa/qrURUweZBIwZYv8O9b2+r4l0HjGAh982/B9sM + > lM05kojyDCUGvj86z18Lm8mKr4/y+i0nJ+vDIksEvfDuzw5ALAXGcBzPJKtICUf7 + > LstA/n9NNpshWz0kld9ylnB5mbUzSFDncVyeXkEf5sGQXdIIZT9ChRBoiloSaa7d + > vBVCcsX1LGP2LWqKtD+7nUnw5qCwtyAVT8pthEUxFTpywoiJS5ZdzeEx8MNGvUeL + > Fj2kleqPF78EioEQlSOxViCuctEtnQuPcDLHNFr10byTZY9roObiqdsJLMVvb2Xl + > iJjAqaPa9AkYwGE6xHw2ispwg64Rse0+AtKups19WIU= + > -----END CERTIFICATE----- + > EOF + +Dump an example certificate (example-leaf.crt) + + $ certigo dump example-leaf.crt + ** CERTIFICATE 1 ** + Not Before: 2016-06-10 22:14:11 +0000 UTC + Not After : 2023-04-15 22:14:11 +0000 UTC + Subject: C=US, ST=CA, O=certigo, OU=example, CN=example-leaf + Issuer: C=US, ST=CA, O=certigo, OU=example, CN=example-leaf + Alternate DNS Names: + \tlocalhost (esc) + Alternate IP Addresses: + \t127.0.0.1 (esc) + \t::1 (esc) + diff --git a/tests/dump-leaf-to-text.t b/tests/dump-leaf-to-text.t index 602b216..3a5da80 100644 --- a/tests/dump-leaf-to-text.t +++ b/tests/dump-leaf-to-text.t @@ -26,7 +26,7 @@ Set up test data. Dump an example certificate (example-leaf.crt) - $ certigo dump example-leaf.crt + $ certigo --verbose dump example-leaf.crt ** CERTIFICATE 1 ** Serial: 15384458167827828543 Not Before: 2016-06-10 22:14:11 +0000 UTC diff --git a/tests/dump-pkcs12-to-pem.t b/tests/dump-pkcs12-to-pem.t index 710ce5d..729dffe 100644 --- a/tests/dump-pkcs12-to-pem.t +++ b/tests/dump-pkcs12-to-pem.t @@ -58,7 +58,7 @@ Set up test data. Dump PEM blocks from a PKCS12 keystore. - $ certigo dump --pem --password password example.p12 + $ certigo --verbose dump --pem --password password example.p12 -----BEGIN CERTIFICATE----- MIIDLDCCAhQCCQCa74bQsAj2/jANBgkqhkiG9w0BAQsFADBYMQswCQYDVQQGEwJV UzELMAkGA1UECBMCQ0ExEDAOBgNVBAoTB2NlcnRpZ28xEDAOBgNVBAsTB2V4YW1w diff --git a/tests/dump-small-key-to-text.t b/tests/dump-small-key-to-text.t index 35b30f3..84a668f 100644 --- a/tests/dump-small-key-to-text.t +++ b/tests/dump-small-key-to-text.t @@ -19,7 +19,7 @@ Set up test data. Dump an example certificate (example-leaf.crt) - $ certigo dump example-small-key.crt + $ certigo --verbose dump example-small-key.crt ** CERTIFICATE 1 ** Serial: 14381893493177441266 Not Before: 2016-06-10 22:14:12 +0000 UTC