1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161
|
package scan
import (
"bytes"
"crypto/tls"
"crypto/x509"
"fmt"
"time"
"github.com/cloudflare/cfssl/helpers"
"github.com/cloudflare/cfssl/revoke"
)
// PKI contains scanners for the Public Key Infrastructure.
var PKI = &Family{
Description: "Scans for the Public Key Infrastructure",
Scanners: map[string]*Scanner{
"ChainExpiration": {
"Host's chain hasn't expired and won't expire in the next 30 days",
chainExpiration,
},
"ChainValidation": {
"All certificates in host's chain are valid",
chainValidation,
},
"MultipleCerts": {
"Host serves same certificate chain across all IPs",
multipleCerts,
},
},
}
// getChain is a helper function that retreives the host's certificate chain.
func getChain(addr string, config *tls.Config) (chain []*x509.Certificate, err error) {
var conn *tls.Conn
conn, err = tls.DialWithDialer(Dialer, Network, addr, config)
if err != nil {
return
}
err = conn.Close()
if err != nil {
return
}
chain = conn.ConnectionState().PeerCertificates
if len(chain) == 0 {
err = fmt.Errorf("%s returned empty certificate chain", addr)
}
return
}
type expiration time.Time
func (e expiration) String() string {
return time.Time(e).Format("Jan 2 15:04:05 2006 MST")
}
func chainExpiration(addr, hostname string) (grade Grade, output Output, err error) {
chain, err := getChain(addr, defaultTLSConfig(hostname))
if err != nil {
return
}
expirationTime := helpers.ExpiryTime(chain)
output = expirationTime
if time.Now().After(expirationTime) {
return
}
// Warn if cert will expire in the next 30 days
if time.Now().Add(time.Hour * 24 * 30).After(expirationTime) {
grade = Warning
return
}
grade = Good
return
}
func chainValidation(addr, hostname string) (grade Grade, output Output, err error) {
chain, err := getChain(addr, defaultTLSConfig(hostname))
if err != nil {
return
}
var warnings []string
for i := 0; i < len(chain)-1; i++ {
cert, parent := chain[i], chain[i+1]
valid := helpers.ValidExpiry(cert)
if !valid {
warnings = append(warnings, fmt.Sprintf("Certificate for %s is valid for too long", cert.Subject.CommonName))
}
revoked, ok := revoke.VerifyCertificate(cert)
if !ok {
warnings = append(warnings, fmt.Sprintf("couldn't check if %s is revoked", cert.Subject.CommonName))
}
if revoked {
err = fmt.Errorf("%s is revoked", cert.Subject.CommonName)
return
}
if !parent.IsCA {
err = fmt.Errorf("%s is not a CA", parent.Subject.CommonName)
return
}
if !bytes.Equal(cert.AuthorityKeyId, parent.SubjectKeyId) {
err = fmt.Errorf("%s AuthorityKeyId differs from %s SubjectKeyId", cert.Subject.CommonName, parent.Subject.CommonName)
return
}
if err = cert.CheckSignatureFrom(parent); err != nil {
return
}
switch cert.SignatureAlgorithm {
case x509.ECDSAWithSHA1:
warnings = append(warnings, fmt.Sprintf("%s is signed by ECDSAWithSHA1", cert.Subject.CommonName))
case x509.SHA1WithRSA:
warnings = append(warnings, fmt.Sprintf("%s is signed by RSAWithSHA1", cert.Subject.CommonName))
}
}
if len(warnings) == 0 {
grade = Good
} else {
grade = Warning
output = warnings
}
return
}
func multipleCerts(addr, hostname string) (grade Grade, output Output, err error) {
config := defaultTLSConfig(hostname)
firstChain, err := getChain(addr, config)
if err != nil {
return
}
grade, _, err = multiscan(addr, func(addrport string) (g Grade, o Output, e error) {
g = Good
chain, e1 := getChain(addrport, config)
if e1 != nil {
return
}
if !chain[0].Equal(firstChain[0]) {
e = fmt.Errorf("%s not equal to %s", chain[0].Subject.CommonName, firstChain[0].Subject.CommonName)
g = Bad
return
}
return
})
return
}
|