123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428 |
- package scan
- import (
- "bytes"
- "errors"
- "fmt"
- "net"
- "strings"
- "github.com/cloudflare/cfssl/helpers"
- "github.com/cloudflare/cfssl/scan/crypto/tls"
- )
- // Sentinel for failures in sayHello. Should always be caught.
- var errHelloFailed = errors.New("Handshake failed in sayHello")
- // TLSHandshake contains scanners testing host cipher suite negotiation
- var TLSHandshake = &Family{
- Description: "Scans for host's SSL/TLS version and cipher suite negotiation",
- Scanners: map[string]*Scanner{
- "CipherSuite": {
- "Determines host's cipher suites accepted and preferred order",
- cipherSuiteScan,
- },
- "SigAlgs": {
- "Determines host's accepted signature and hash algorithms",
- sigAlgsScan,
- },
- "CertsBySigAlgs": {
- "Determines host's certificate signature algorithm matching client's accepted signature and hash algorithms",
- certSigAlgsScan,
- },
- "CertsByCiphers": {
- "Determines host's certificate signature algorithm matching client's accepted ciphers",
- certSigAlgsScanByCipher,
- },
- "ECCurves": {
- "Determines the host's ec curve support for TLS 1.2",
- ecCurveScan,
- },
- },
- }
- func getCipherIndex(ciphers []uint16, serverCipher uint16) (cipherIndex int, err error) {
- //func getCipherIndex(ciphers []uint16, serverCipher uint16) (cipherIndex int, err error) {
- // fmt.Println(serverCipher, ciphers)
- var cipherID uint16
- for cipherIndex, cipherID = range ciphers {
- if serverCipher == cipherID {
- return
- }
- }
- err = fmt.Errorf("server negotiated ciphersuite we didn't send: %s", tls.CipherSuites[serverCipher])
- return
- }
- func getCurveIndex(curves []tls.CurveID, serverCurve tls.CurveID) (curveIndex int, err error) {
- var curveID tls.CurveID
- for curveIndex, curveID = range curves {
- if serverCurve == curveID {
- return
- }
- }
- err = fmt.Errorf("server negotiated elliptic curve we didn't send: %s", tls.Curves[serverCurve])
- return
- }
- func sayHello(addr, hostname string, ciphers []uint16, curves []tls.CurveID, vers uint16, sigAlgs []tls.SignatureAndHash) (cipherIndex, curveIndex int, certs [][]byte, err error) {
- tcpConn, err := net.Dial(Network, addr)
- if err != nil {
- return
- }
- config := defaultTLSConfig(hostname)
- config.MinVersion = vers
- config.MaxVersion = vers
- if ciphers == nil {
- ciphers = allCiphersIDs()
- }
- config.CipherSuites = ciphers
- if curves == nil {
- curves = allCurvesIDs()
- }
- config.CurvePreferences = curves
- if sigAlgs == nil {
- sigAlgs = tls.AllSignatureAndHashAlgorithms
- }
- conn := tls.Client(tcpConn, config)
- serverCipher, serverCurveType, serverCurve, serverVersion, certificates, err := conn.SayHello(sigAlgs)
- certs = certificates
- conn.Close()
- if err != nil {
- err = errHelloFailed
- return
- }
- if serverVersion != vers {
- err = fmt.Errorf("server negotiated protocol version we didn't send: %s", tls.Versions[serverVersion])
- return
- }
- cipherIndex, err = getCipherIndex(ciphers, serverCipher)
- if tls.CipherSuites[serverCipher].EllipticCurve {
- if curves == nil {
- curves = allCurvesIDs()
- }
- if serverCurveType != 3 {
- err = fmt.Errorf("server negotiated non-named ECDH parameters; we didn't analyze them. Server curve type: %d", serverCurveType)
- return
- }
- curveIndex, err = getCurveIndex(curves, serverCurve)
- }
- return
- }
- func allCiphersIDs() []uint16 {
- ciphers := make([]uint16, 0, len(tls.CipherSuites))
- for cipherID := range tls.CipherSuites {
- ciphers = append(ciphers, cipherID)
- }
- return ciphers
- }
- func allECDHECiphersIDs() []uint16 {
- var ecdheCiphers = map[uint16]tls.CipherSuite{
- 0XC006: {Name: "TLS_ECDHE_ECDSA_WITH_NULL_SHA", ForwardSecret: true, EllipticCurve: true},
- 0XC007: {Name: "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", ShortName: "ECDHE-ECDSA-RC4-SHA", ForwardSecret: true, EllipticCurve: true},
- 0XC008: {Name: "TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA", ShortName: "ECDHE-ECDSA-DES-CBC3-SHA", ForwardSecret: true, EllipticCurve: true},
- 0XC009: {Name: "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", ShortName: "ECDHE-ECDSA-AES128-SHA", ForwardSecret: true, EllipticCurve: true},
- 0XC00A: {Name: "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", ShortName: "ECDHE-ECDSA-AES256-SHA", ForwardSecret: true, EllipticCurve: true},
- 0XC010: {Name: "TLS_ECDHE_RSA_WITH_NULL_SHA", ForwardSecret: true, EllipticCurve: true},
- 0XC011: {Name: "TLS_ECDHE_RSA_WITH_RC4_128_SHA", ShortName: "ECDHE-RSA-RC4-SHA", ForwardSecret: true, EllipticCurve: true},
- 0XC012: {Name: "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", ShortName: "ECDHE-RSA-DES-CBC3-SHA", ForwardSecret: true, EllipticCurve: true},
- 0XC013: {Name: "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", ShortName: "ECDHE-RSA-AES128-SHA", ForwardSecret: true, EllipticCurve: true},
- 0XC014: {Name: "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", ShortName: "ECDHE-RSA-AES256-SHA", ForwardSecret: true, EllipticCurve: true},
- 0XC023: {Name: "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", ShortName: "ECDHE-ECDSA-AES128-SHA256", ForwardSecret: true, EllipticCurve: true},
- 0XC024: {Name: "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384", ShortName: "ECDHE-ECDSA-AES256-SHA384", ForwardSecret: true, EllipticCurve: true},
- 0XC027: {Name: "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", ShortName: "ECDHE-RSA-AES128-SHA256", ForwardSecret: true, EllipticCurve: true},
- 0XC028: {Name: "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", ShortName: "ECDHE-RSA-AES256-SHA384", ForwardSecret: true, EllipticCurve: true},
- 0XC02B: {Name: "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", ShortName: "ECDHE-ECDSA-AES128-GCM-SHA256", ForwardSecret: true, EllipticCurve: true},
- 0XC02C: {Name: "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", ShortName: "ECDHE-ECDSA-AES256-GCM-SHA384", ForwardSecret: true, EllipticCurve: true},
- 0XC02F: {Name: "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", ShortName: "ECDHE-RSA-AES128-GCM-SHA256", ForwardSecret: true, EllipticCurve: true},
- 0XC030: {Name: "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", ShortName: "ECDHE-RSA-AES256-GCM-SHA384", ForwardSecret: true, EllipticCurve: true},
- 0XC048: {Name: "TLS_ECDHE_ECDSA_WITH_ARIA_128_CBC_SHA256", ForwardSecret: true, EllipticCurve: true},
- 0XC049: {Name: "TLS_ECDHE_ECDSA_WITH_ARIA_256_CBC_SHA384", ForwardSecret: true, EllipticCurve: true},
- 0XC04C: {Name: "TLS_ECDHE_RSA_WITH_ARIA_128_CBC_SHA256", ForwardSecret: true, EllipticCurve: true},
- 0XC04D: {Name: "TLS_ECDHE_RSA_WITH_ARIA_256_CBC_SHA384", ForwardSecret: true, EllipticCurve: true},
- 0XC05D: {Name: "TLS_ECDHE_ECDSA_WITH_ARIA_256_GCM_SHA384", ForwardSecret: true, EllipticCurve: true},
- 0XC060: {Name: "TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256", ForwardSecret: true, EllipticCurve: true},
- 0XC061: {Name: "TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384", ForwardSecret: true, EllipticCurve: true},
- 0XC072: {Name: "TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256", ForwardSecret: true, EllipticCurve: true},
- 0XC073: {Name: "TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384", ForwardSecret: true, EllipticCurve: true},
- 0XC076: {Name: "TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256", ForwardSecret: true, EllipticCurve: true},
- 0XC077: {Name: "TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384", ForwardSecret: true, EllipticCurve: true},
- 0XC086: {Name: "TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256", ForwardSecret: true, EllipticCurve: true},
- 0XC087: {Name: "TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_GCM_SHA384", ForwardSecret: true, EllipticCurve: true},
- 0XC08A: {Name: "TLS_ECDHE_RSA_WITH_CAMELLIA_128_GCM_SHA256", ForwardSecret: true, EllipticCurve: true},
- 0XC08B: {Name: "TLS_ECDHE_RSA_WITH_CAMELLIA_256_GCM_SHA384", ForwardSecret: true, EllipticCurve: true},
- 0XC08C: {Name: "TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256", EllipticCurve: true},
- 0XC0AC: {Name: "TLS_ECDHE_ECDSA_WITH_AES_128_CCM", ForwardSecret: true, EllipticCurve: true},
- 0XC0AD: {Name: "TLS_ECDHE_ECDSA_WITH_AES_256_CCM", ForwardSecret: true, EllipticCurve: true},
- 0XC0AE: {Name: "TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8", ForwardSecret: true, EllipticCurve: true},
- 0XC0AF: {Name: "TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8", ForwardSecret: true, EllipticCurve: true},
- // Non-IANA standardized cipher suites:
- // ChaCha20, Poly1305 cipher suites are defined in
- // https://tools.ietf.org/html/draft-agl-tls-chacha20poly1305-04
- 0XCC13: {Name: "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", ForwardSecret: true, EllipticCurve: true},
- 0XCC14: {Name: "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", ForwardSecret: true, EllipticCurve: true},
- }
- ciphers := make([]uint16, 0, len(ecdheCiphers))
- for cipherID := range ecdheCiphers {
- ciphers = append(ciphers, cipherID)
- }
- return ciphers
- }
- func allCurvesIDs() []tls.CurveID {
- curves := make([]tls.CurveID, 0, len(tls.Curves))
- for curveID := range tls.Curves {
- // No unassigned or explicit curves in the scan, per http://tools.ietf.org/html/rfc4492#section-5.4
- if curveID == 0 || curveID == 65281 || curveID == 65282 {
- continue
- } else {
- curves = append(curves, curveID)
- }
- }
- return curves
- }
- type cipherDatum struct {
- versionID uint16
- curves []tls.CurveID
- }
- // cipherVersions contains lists of host's supported cipher suites based on SSL/TLS Version.
- // If a cipher suite uses ECC, also contains a list of supported curves by SSL/TLS Version.
- type cipherVersions struct {
- cipherID uint16
- data []cipherDatum
- }
- type cipherVersionList []cipherVersions
- func (cvList cipherVersionList) String() string {
- cvStrings := make([]string, len(cvList))
- for i, c := range cvList {
- versStrings := make([]string, len(c.data))
- for j, d := range c.data {
- curveStrings := make([]string, len(d.curves))
- for k, c := range d.curves {
- curveStrings[k] = tls.Curves[c]
- }
- versStrings[j] = fmt.Sprintf("%s: [ %s ]", tls.Versions[d.versionID], strings.Join(curveStrings, ","))
- }
- cvStrings[i] = fmt.Sprintf("%s\t%s", tls.CipherSuites[c.cipherID], strings.Join(versStrings, ","))
- }
- return strings.Join(cvStrings, "\n")
- }
- func (cvList cipherVersionList) MarshalJSON() ([]byte, error) {
- b := new(bytes.Buffer)
- cvStrs := make([]string, len(cvList))
- for i, cv := range cvList {
- versStrings := make([]string, len(cv.data))
- for j, d := range cv.data {
- curveStrings := make([]string, len(d.curves))
- if len(d.curves) > 0 {
- for k, c := range d.curves {
- curveStrings[k] = fmt.Sprintf("\"%s\"", tls.Curves[c])
- }
- versStrings[j] = fmt.Sprintf("{\"%s\":[%s]}", tls.Versions[d.versionID], strings.Join(curveStrings, ","))
- } else {
- versStrings[j] = fmt.Sprintf("\"%s\"", tls.Versions[d.versionID])
- }
- }
- cvStrs[i] = fmt.Sprintf("{\"%s\":[%s]}", tls.CipherSuites[cv.cipherID].String(), strings.Join(versStrings, ","))
- }
- fmt.Fprintf(b, "[%s]", strings.Join(cvStrs, ","))
- return b.Bytes(), nil
- }
- func doCurveScan(addr, hostname string, vers, cipherID uint16, ciphers []uint16) (supportedCurves []tls.CurveID, err error) {
- allCurves := allCurvesIDs()
- curves := make([]tls.CurveID, len(allCurves))
- copy(curves, allCurves)
- for len(curves) > 0 {
- var curveIndex int
- _, curveIndex, _, err = sayHello(addr, hostname, []uint16{cipherID}, curves, vers, nil)
- if err != nil {
- // This case is expected, because eventually we ask only for curves the server doesn't support
- if err == errHelloFailed {
- err = nil
- break
- }
- return
- }
- curveID := curves[curveIndex]
- supportedCurves = append(supportedCurves, curveID)
- curves = append(curves[:curveIndex], curves[curveIndex+1:]...)
- }
- return
- }
- // cipherSuiteScan returns, by TLS Version, the sort list of cipher suites
- // supported by the host
- func cipherSuiteScan(addr, hostname string) (grade Grade, output Output, err error) {
- var cvList cipherVersionList
- allCiphers := allCiphersIDs()
- var vers uint16
- for vers = tls.VersionTLS12; vers >= tls.VersionSSL30; vers-- {
- ciphers := make([]uint16, len(allCiphers))
- copy(ciphers, allCiphers)
- for len(ciphers) > 0 {
- var cipherIndex int
- cipherIndex, _, _, err = sayHello(addr, hostname, ciphers, nil, vers, nil)
- if err != nil {
- if err == errHelloFailed {
- err = nil
- break
- }
- return
- }
- if vers == tls.VersionSSL30 {
- grade = Warning
- }
- cipherID := ciphers[cipherIndex]
- // If this is an EC cipher suite, do a second scan for curve support
- var supportedCurves []tls.CurveID
- if tls.CipherSuites[cipherID].EllipticCurve {
- supportedCurves, err = doCurveScan(addr, hostname, vers, cipherID, ciphers)
- if len(supportedCurves) == 0 {
- err = errors.New("couldn't negotiate any curves")
- }
- }
- for i, c := range cvList {
- if cipherID == c.cipherID {
- cvList[i].data = append(c.data, cipherDatum{vers, supportedCurves})
- goto exists
- }
- }
- cvList = append(cvList, cipherVersions{cipherID, []cipherDatum{{vers, supportedCurves}}})
- exists:
- ciphers = append(ciphers[:cipherIndex], ciphers[cipherIndex+1:]...)
- }
- }
- if len(cvList) == 0 {
- err = errors.New("couldn't negotiate any cipher suites")
- return
- }
- if grade != Warning {
- grade = Good
- }
- output = cvList
- return
- }
- // sigAlgsScan returns the accepted signature and hash algorithms of the host
- func sigAlgsScan(addr, hostname string) (grade Grade, output Output, err error) {
- var supportedSigAlgs []tls.SignatureAndHash
- for _, sigAlg := range tls.AllSignatureAndHashAlgorithms {
- _, _, _, e := sayHello(addr, hostname, nil, nil, tls.VersionTLS12, []tls.SignatureAndHash{sigAlg})
- if e == nil {
- supportedSigAlgs = append(supportedSigAlgs, sigAlg)
- }
- }
- if len(supportedSigAlgs) > 0 {
- grade = Good
- output = supportedSigAlgs
- } else {
- err = errors.New("no SigAlgs supported")
- }
- return
- }
- // certSigAlgScan returns the server certificate with various sigature and hash algorithms in the ClientHello
- func certSigAlgsScan(addr, hostname string) (grade Grade, output Output, err error) {
- var certSigAlgs = make(map[string]string)
- for _, sigAlg := range tls.AllSignatureAndHashAlgorithms {
- _, _, derCerts, e := sayHello(addr, hostname, nil, nil, tls.VersionTLS12, []tls.SignatureAndHash{sigAlg})
- if e == nil {
- if len(derCerts) == 0 {
- return Bad, nil, errors.New("no certs returned")
- }
- certs, _, err := helpers.ParseCertificatesDER(derCerts[0], "")
- if err != nil {
- return Bad, nil, err
- }
- certSigAlgs[sigAlg.String()] = helpers.SignatureString(certs[0].SignatureAlgorithm)
- //certSigAlgs = append(certSigAlgs, certs[0].SignatureAlgorithm)
- }
- }
- if len(certSigAlgs) > 0 {
- grade = Good
- output = certSigAlgs
- } else {
- err = errors.New("no SigAlgs supported")
- }
- return
- }
- // certSigAlgScan returns the server certificate with various ciphers in the ClientHello
- func certSigAlgsScanByCipher(addr, hostname string) (grade Grade, output Output, err error) {
- var certSigAlgs = make(map[string]string)
- for cipherID := range tls.CipherSuites {
- _, _, derCerts, e := sayHello(addr, hostname, []uint16{cipherID}, nil, tls.VersionTLS12, []tls.SignatureAndHash{})
- if e == nil {
- if len(derCerts) == 0 {
- return Bad, nil, errors.New("no certs returned")
- }
- certs, _, err := helpers.ParseCertificatesDER(derCerts[0], "")
- if err != nil {
- return Bad, nil, err
- }
- certSigAlgs[tls.CipherSuites[cipherID].Name] = helpers.SignatureString(certs[0].SignatureAlgorithm)
- //certSigAlgs = append(certSigAlgs, certs[0].SignatureAlgorithm)
- }
- }
- if len(certSigAlgs) > 0 {
- grade = Good
- output = certSigAlgs
- } else {
- err = errors.New("no cipher supported")
- }
- return
- }
- // ecCurveScan returns the elliptic curves supported by the host.
- func ecCurveScan(addr, hostname string) (grade Grade, output Output, err error) {
- allCurves := allCurvesIDs()
- curves := make([]tls.CurveID, len(allCurves))
- copy(curves, allCurves)
- var supportedCurves []string
- for len(curves) > 0 {
- var curveIndex int
- _, curveIndex, _, err = sayHello(addr, hostname, allECDHECiphersIDs(), curves, tls.VersionTLS12, nil)
- if err != nil {
- // This case is expected, because eventually we ask only for curves the server doesn't support
- if err == errHelloFailed {
- err = nil
- break
- }
- return
- }
- curveID := curves[curveIndex]
- supportedCurves = append(supportedCurves, tls.Curves[curveID])
- curves = append(curves[:curveIndex], curves[curveIndex+1:]...)
- }
- output = supportedCurves
- grade = Good
- return
- }
|