Merge pull request #119 from square/mmc/tlsinfo

Print TLS version & cipher suite when connecting
This commit is contained in:
Matthew McPherrin
2017-02-21 13:14:52 -08:00
committed by GitHub
7 changed files with 135 additions and 5 deletions
+1
View File
@@ -1 +1,2 @@
certigo
certigo.exe
-1
View File
@@ -3,7 +3,6 @@
language: go
go:
- 1.7.5
- 1.8
install:
+5 -2
View File
@@ -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
+118
View File
@@ -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},
}
+2
View File
@@ -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))
+1
View File
@@ -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 != "" {
+8 -2
View File
@@ -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)
}