diff --git a/.gitignore b/.gitignore index f35548d..95864b0 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ certigo +certigo.exe \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 849326d..6477d5e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,6 @@ language: go go: - - 1.7.5 - 1.8 install: diff --git a/README.md b/README.md index acfd25a..9310054 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Certigo is a utility to examine and validate certificates to help with debugging **Validation and linting**: Not sure if your generated certificate is valid? Certigo can connect to remote servers to display and validate their certificate chains. It can also point out common errors on certififcates, such as using an older X.509 format, signatures with outdated hashes, or keys that are too small. -**Supports MySQL and PostgreSQL**: Trying to debug SSL/TLS connections on a database? Certigo supports establishing connections via StartTLS protocols for MySQL and PostgreSQL, making it possible debug connection issues or scan for expired certificates more easily. +**Supports STARTTLS Protocols**: Trying to debug SSL/TLS connections on a database or mail server? Certigo supports establishing connections via StartTLS protocols for MySQL, PostgreSQL, SMTP, and LDAP, making it possible debug connection issues or scan for expired certificates more easily. **Scripting support**: All commands in certigo have support for optional JSON output, which can be used in shell scripts to analyze or filter output. Combine certigo with [jq](https://stedolan.github.io/jq) to find all certificates in a bundle that are signed with SHA1-RSA, or filter for CA certificates, or whatever you need! @@ -27,7 +27,7 @@ On macOS you can also use homebrew to install: brew install certigo -Note that certigo requires Go 1.5 or later to build. +Note that certigo requires Go 1.8 or later to build. ### Develop @@ -136,6 +136,9 @@ Display & validate certificates from a remote server (also supports `--start-tls ``` $ certigo connect squareup.com:443 +** TLS Connection ** +Version: TLS 1.2 +Cipher Suite: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 ** CERTIFICATE 1 ** Serial: 260680855742043049380997676879525498489 Not Before: 2016-07-15 20:15:52 +0000 UTC diff --git a/lib/tls.go b/lib/tls.go new file mode 100644 index 0000000..9daafd3 --- /dev/null +++ b/lib/tls.go @@ -0,0 +1,118 @@ +package lib + +import ( + "bufio" + "bytes" + "crypto/tls" + "fmt" + "text/template" + + "github.com/fatih/color" +) + +// TLSDescription has the basic information about a TLS connection +type TLSDescription struct { + Version string + Cipher string +} + +var tlsLayout = `** TLS Connection ** +Version: {{.Version}} +Cipher Suite: {{.Cipher}}` + +func tlscolor(d description) string { + c, ok := qualityColors[d.Quality] + if !ok { + return d.Name + } + return c.SprintFunc()(d.Name) +} + +// EncodeTLSToText returns a human readable string, suitable for certigo console output. +func EncodeTLSToText(tcs *tls.ConnectionState) string { + version := lookup(tlsVersions, tcs.Version) + cipher := lookup(cipherSuites, tcs.CipherSuite) + description := TLSDescription{ + Version: tlscolor(version), + Cipher: tlscolor(cipher), + } + t := template.New("TLS template") + t, err := t.Parse(tlsLayout) + if err != nil { + // Should never happen + panic(err) + } + var buffer bytes.Buffer + w := bufio.NewWriter(&buffer) + err = t.Execute(w, description) + if err != nil { + // Should never happen + panic(err) + } + w.Flush() + return string(buffer.Bytes()) +} + +// EncodeTLSToObject returns a JSON-marshallable description of a TLS connection +func EncodeTLSToObject(t *tls.ConnectionState) interface{} { + version := lookup(tlsVersions, t.Version) + cipher := lookup(cipherSuites, t.CipherSuite) + return &TLSDescription{ + version.Slug, + cipher.Slug, + } +} + +// Just a map lookup with a default +func lookup(descriptions map[uint16]description, what uint16) description { + v, ok := descriptions[what] + if !ok { + unknown := fmt.Sprintf("UNKNOWN_%x", what) + return description{unknown, unknown, 0} + } + return v +} + +type description struct { + Name string // a human-friendly string + Slug string // a machine-friendly string + Quality uint8 // 0 = insecure, 1 = ok, 2 = good +} + +var qualityColors = map[uint8]*color.Color{ + 0: red, + 1: yellow, + 2: green, +} + +var tlsVersions = map[uint16]description{ + tls.VersionSSL30: {"SSL 3.0", "ssl_3_0", 0}, + tls.VersionTLS10: {"TLS 1.0", "tls_1_0", 0}, + tls.VersionTLS11: {"TLS 1.1", "tls_1_1", 1}, + tls.VersionTLS12: {"TLS 1.2", "tls_1_2", 2}, +} + +var cipherSuites = map[uint16]description{ + tls.TLS_RSA_WITH_RC4_128_SHA: {"TLS_RSA_WITH_RC4_128_SHA", "TLS_RSA_WITH_RC4_128_SHA", 0}, + tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA: {"TLS_RSA_WITH_3DES_EDE_CBC_SHA", "TLS_RSA_WITH_3DES_EDE_CBC_SHA", 0}, + tls.TLS_RSA_WITH_AES_128_CBC_SHA: {"TLS_RSA_WITH_AES_128_CBC_SHA", "TLS_RSA_WITH_AES_128_CBC_SHA", 1}, + tls.TLS_RSA_WITH_AES_256_CBC_SHA: {"TLS_RSA_WITH_AES_256_CBC_SHA", "TLS_RSA_WITH_AES_256_CBC_SHA", 1}, + tls.TLS_RSA_WITH_AES_128_CBC_SHA256: {"TLS_RSA_WITH_AES_128_CBC_SHA256", "TLS_RSA_WITH_AES_128_CBC_SHA256", 1}, + tls.TLS_RSA_WITH_AES_128_GCM_SHA256: {"TLS_RSA_WITH_AES_128_GCM_SHA256", "TLS_RSA_WITH_AES_128_GCM_SHA256", 1}, + tls.TLS_RSA_WITH_AES_256_GCM_SHA384: {"TLS_RSA_WITH_AES_256_GCM_SHA384", "TLS_RSA_WITH_AES_256_GCM_SHA384", 1}, + tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA: {"TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", 0}, + tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA: {"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", 1}, + tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA: {"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", 1}, + tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA: {"TLS_ECDHE_RSA_WITH_RC4_128_SHA", "TLS_ECDHE_RSA_WITH_RC4_128_SHA", 0}, + tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA: {"TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", 0}, + tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA: {"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", 1}, + tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA: {"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", 1}, + tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256: {"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", 1}, + tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256: {"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", 1}, + tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256: {"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", 2}, + tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256: {"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", 2}, + tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384: {"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", 2}, + tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384: {"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", 2}, + tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305: {"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305", "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305", 2}, + tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305: {"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305", "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305", 2}, +} diff --git a/main.go b/main.go index c9348ea..fd1b0b1 100644 --- a/main.go +++ b/main.go @@ -101,6 +101,7 @@ func main() { fmt.Fprintf(os.Stderr, "%s\n", strings.TrimSuffix(err.Error(), "\n")) os.Exit(1) } + result.TLSConnectionState = connState for _, cert := range connState.PeerCertificates { if *connectPem { pem.Encode(os.Stdout, lib.EncodeX509ToPEM(cert, nil)) @@ -124,6 +125,7 @@ func main() { blob, _ := json.Marshal(result) fmt.Println(string(blob)) } else if !*connectPem { + fmt.Fprintf(stdout, "%s\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)) diff --git a/starttls/starttls.go b/starttls/starttls.go index 1c227a1..a1308db 100644 --- a/starttls/starttls.go +++ b/starttls/starttls.go @@ -31,6 +31,7 @@ func tlsConfigForConnect(connectName, clientCert, clientKey string) (*tls.Config // We verify later manually so we can print results InsecureSkipVerify: true, ServerName: connectName, + MinVersion: tls.VersionSSL30, } if clientCert != "" { diff --git a/verify.go b/verify.go index 93ebfc2..728abb3 100644 --- a/verify.go +++ b/verify.go @@ -26,6 +26,8 @@ import ( "os" "strconv" + "crypto/tls" + "github.com/fatih/color" "github.com/square/certigo/lib" ) @@ -72,8 +74,9 @@ type simpleVerification struct { } type simpleResult struct { - Certificates []*x509.Certificate `json:"certificates"` - VerifyResult *simpleVerification `json:"verify_result,omitempty"` + Certificates []*x509.Certificate `json:"certificates"` + VerifyResult *simpleVerification `json:"verify_result,omitempty"` + TLSConnectionState *tls.ConnectionState } func (s simpleResult) MarshalJSON() ([]byte, error) { @@ -87,6 +90,9 @@ func (s simpleResult) MarshalJSON() ([]byte, error) { if s.VerifyResult != nil { out["verify_result"] = s.VerifyResult } + if s.TLSConnectionState != nil { + out["tls_connection"] = lib.EncodeTLSToObject(s.TLSConnectionState) + } return json.Marshal(out) }