mirror of
https://github.com/square/certigo.git
synced 2024-04-21 12:32:40 +00:00
Merge pull request #119 from square/mmc/tlsinfo
Print TLS version & cipher suite when connecting
This commit is contained in:
@@ -1 +1,2 @@
|
||||
certigo
|
||||
certigo.exe
|
||||
@@ -3,7 +3,6 @@
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.7.5
|
||||
- 1.8
|
||||
|
||||
install:
|
||||
|
||||
@@ -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
@@ -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},
|
||||
}
|
||||
@@ -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))
|
||||
|
||||
@@ -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 != "" {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user