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
35 changes: 30 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ Certigo has commands to dump certificates and keystores from a file, to connect
```
usage: certigo [<flags>] <command> [<args> ...]

A command line certificate examination utility.
A command-line utility to examine and validate certificates to help with debugging SSL/TLS issues.

Flags:
--help Show context-sensitive help (also try --help-long and --help-man).
Expand All @@ -55,24 +55,30 @@ Commands:


dump [<flags>] [<file>...]
Display information about a certificate from a file/stdin.
Display information about a certificate from a file or stdin.

-f, --format=FORMAT Format of given input (PEM, DER, JCEKS, PKCS12; heuristic if missing).
-p, --password=PASSWORD Password for PKCS12/JCEKS key stores (reads from TTY if missing).
-m, --pem Write output as PEM blocks instead of human-readable format.
-j, --json Write output as machine-readable JSON format.
-d, --depth=0 Certificate chain information upto a certain depth.
-c, --csr Parse only Certificate Signing Request(s) in the file(s).

connect [<flags>] [<server:port>]
connect [<flags>] [<server[:port]>]
Connect to a server and print its certificate(s).

-n, --name=NAME Override the server name used for Server Name Indication (SNI).
--ca=CA Path to CA bundle (system default if unspecified).
--cert=CERT Client certificate chain for connecting to server (PEM).
--key=KEY Private key for client certificate, if not in same file (PEM).
-t, --start-tls=PROTOCOL Enable StartTLS protocol ('ldap', 'mysql', 'postgres', 'smtp' or 'ftp').
-t, --start-tls=PROTOCOL Enable StartTLS protocol; one of: [mysql postgres psql smtp ldap ftp imap].
--identity="certigo" With --start-tls, sets the DB user or SMTP EHLO name
--proxy=PROXY Optional URI for HTTP(s) CONNECT proxy to dial connections with
--timeout=5s Timeout for connecting to remote server (can be '5m', '1s', etc).
-m, --pem Write output as PEM blocks instead of human-readable format.
-j, --json Write output as machine-readable JSON format.
--verify Verify certificate chain.
-d, --depth=0 Certificate chain information upto a certain depth.

verify --name=NAME [<flags>] [<file>]
Verify a certificate chain from file/stdin against a name.
Expand All @@ -86,7 +92,7 @@ Commands:

### Examples

Display information about a certificate (also supports `--pem` and `--json` output):
Display information about a certificate (also supports `--pem`, `--depth` and `--json` output):

```
$ certigo dump --verbose squareup-2016.crt
Expand Down Expand Up @@ -125,6 +131,25 @@ Alternate DNS Names:
www.gosq.co
```

Display information about a certificate signing request (also supports `--depth` and `--json` output)

```
$ certigo dump --csr test.csr --verbose
** CERTIFICATE REQUEST 1 **
Signature: ECDSA-SHA256
Subject Info:
Country: IN
Province: KA
Locality: Bangalore
Organization: Certigo
Organizational Unit: InfoSec
CommonName: test.certigo.com
Email Address: a@b.c
Warnings:
Certificate Request is not in X509v3 format (version is 0)
Certificate Request doesn't have any valid DNS/URI names or IP addresses set
```

Display & validate certificates from a remote server (also supports `--start-tls`):

```
Expand Down
9 changes: 9 additions & 0 deletions lib/certs.go
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,15 @@ func EncodeX509ToPEM(cert *x509.Certificate, headers map[string]string) *pem.Blo
}
}

// EncodeX509CSRToPEM converts an X.509 certificate request to a PEM block for output
func EncodeX509CSRToPEM(csr *x509.CertificateRequest, headers map[string]string) *pem.Block {
return &pem.Block{
Type: "CERTIFICATE REQUEST",
Bytes: csr.Raw,
Headers: headers,
}
}

// Convert a PKCS7 envelope into a PEM block for output.
func pkcs7ToPem(block *pkcs7.SignedDataEnvelope, headers map[string]string) *pem.Block {
return &pem.Block{
Expand Down
155 changes: 125 additions & 30 deletions lib/display.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ Authority Key ID: {{.Issuer.KeyID | hexify}}
{{- if .BasicConstraints}}
Basic Constraints: CA:{{.BasicConstraints.IsCA}}{{if .BasicConstraints.MaxPathLen}}, pathlen:{{.BasicConstraints.MaxPathLen}}{{end}}{{end}}
{{- if .NameConstraints}}
Name Constraints{{if .NameConstraints.Critical}} (critical){{end}}:
Name Constraints{{if .NameConstraints.Critical}} (critical){{end}}:
{{- if .NameConstraints.PermittedDNSDomains}}
Permitted DNS domains:
{{wrapWith .Width "\n\t" (join ", " .NameConstraints.PermittedDNSDomains)}}
Expand Down Expand Up @@ -156,6 +156,62 @@ Email Addresses:
Warnings:{{range .Warnings}}
{{. | redify}}{{end}}{{end}}`

var csrLayout = `
{{- if .Version}}{{.Version}}
{{end -}}
Subject:
{{wrapWith .Width "\n\t" (.Subject.Name | printShortName)}}
{{- if .AltDNSNames}}
DNS Names:
{{wrapWith .Width "\n\t" (join ", " .AltDNSNames)}}{{end}}
{{- if .AltIPAddresses}}
{{wrapWith .Width "\n\t" (join ", " .AltIPAdresses)}}{{end}}
{{- if .URINames}}
URI Names:
{{wrapWith .Width "\n\t" (join ", " .URINames)}}{{end}}
{{- if .EmailAddresses}}
Email Addresses:
{{wrapWith .Width "\n\t" (join ", " .EmailAddresses)}}{{end}}
{{- if .Warnings}}
Warnings:{{range .Warnings}}
{{. | redify}}{{end}}{{end}}`

var verboseCSRLayout = `
{{- define "PkixName" -}}
{{- range .Names}}
{{ .Type | oidName }}: {{ .Value }}
{{- end -}}
{{end -}}

Signature: {{.SignatureAlgorithm | highlightAlgorithm}}
Subject Info:
{{- template "PkixName" .Subject.Name}}
{{- if .AltDNSNames}}
{{- range .Extensions}}
{{ .Id | oidName}}: {{.Value}}
{{- end -}}
DNS Names:
{{wrapWith .Width "\n\t" (join ", " .AltDNSNames)}}
{{- end}}
{{- if .AltIPAddresses}}
IP Addresses:
{{wrapWith .Width "\n\t" (join ", " .AltIPAddresses)}}
{{- end}}
{{- if .URINames}}
URI Names:
{{wrapWith .Width "\n\t" (join ", " .URINames)}}
{{- end}}
{{- if .EmailAddresses}}
Email Addresses:
{{wrapWith .Width "\n\t" (join ", " .EmailAddresses)}}
{{- end}}
{{- if .Warnings}}
Warnings:
{{- range .Warnings}}
{{. | redify}}
{{- end}}
{{- end}}`

type certWithName struct {
name string
file string
Expand Down Expand Up @@ -200,52 +256,91 @@ func EncodeX509ToObject(cert *x509.Certificate) interface{} {
}

// EncodeX509ToText encodes an X.509 certificate into human-readable text.
func EncodeX509ToText(cert *x509.Certificate, terminalWidth int, verbose bool) []byte {
c := createSimpleCertificate("", cert)
c.Width = terminalWidth - 8 /* Need some margin for tab */

return displayCert(c, verbose)
func EncodeX509ToText(obj interface{}, terminalWidth int, verbose bool) []byte {
switch obj.(type) {
case *x509.Certificate:
obj, ok := obj.(*x509.Certificate)
if ok {
c := createSimpleCertificate("", obj)
c.Width = terminalWidth - 8 /* Need some margin for tab */
return displayCert(c, verbose)
}
case *x509.CertificateRequest:
obj, ok := obj.(*x509.CertificateRequest)
if ok {
c := createSimpleCertificateRequest("", obj)
c.Width = terminalWidth - 8 /* Need some margin for tab */
return displayCert(c, verbose)
}
}
// should never happen
return nil
}

// 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, verbose bool) []byte {
func displayCert(obj interface{}, verbose bool) []byte {
// Use template functions from sprig, but add some extras
funcMap := sprig.TxtFuncMap()
var t *template.Template
var err error

extras := template.FuncMap{
"certStart": certStart,
"certEnd": certEnd,
"redify": redify,
"highlightAlgorithm": highlightAlgorithm,
"hexify": hexify,
"keyUsage": keyUsage,
"extKeyUsage": extKeyUsage,
"oidName": oidName,
"oidShort": oidShort,
"printShortName": PrintShortName,
"printCommonName": PrintCommonName,
}
for k, v := range extras {
funcMap[k] = v
}
switch obj.(type) {
case simpleCertificate:
extras := template.FuncMap{
"certStart": certStart,
"certEnd": certEnd,
"redify": redify,
"highlightAlgorithm": highlightAlgorithm,
"hexify": hexify,
"keyUsage": keyUsage,
"extKeyUsage": extKeyUsage,
"oidName": oidName,
"oidShort": oidShort,
"printShortName": PrintShortName,
"printCommonName": PrintCommonName,
}
for k, v := range extras {
funcMap[k] = v
}

t := template.New("Cert template").Funcs(funcMap)
var err error
if verbose {
t, err = t.Parse(verboseLayout)
} else {
t, err = t.Parse(layout)
t = template.New("Cert template").Funcs(funcMap)
if verbose {
t, err = t.Parse(verboseLayout)
} else {
t, err = t.Parse(layout)
}
case simpleCertificateRequest:
extras := template.FuncMap{
"redify": redify,
"highlightAlgorithm": highlightAlgorithm,
"hexify": hexify,
"oidName": oidName,
"oidShort": oidShort,
"printShortName": PrintShortName,
"printCommonName": PrintCommonName,
}
for k, v := range extras {
funcMap[k] = v
}

t = template.New("Cert Request template").Funcs(funcMap)
if verbose {
t, err = t.Parse(verboseCSRLayout)
} else {
t, err = t.Parse(csrLayout)
}
}

if err != nil {
// Should never happen
panic(err)
}
var buffer bytes.Buffer
w := bufio.NewWriter(&buffer)
err = t.Execute(w, cert)
err = t.Execute(w, obj)
if err != nil {
// Should never happen
panic(err)
Expand Down
Loading