go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/providers/network/resources/tlsshake/tlsshake.go (about)

     1  // Copyright (c) Mondoo, Inc.
     2  // SPDX-License-Identifier: BUSL-1.1
     3  
     4  // shake that SSL
     5  
     6  package tlsshake
     7  
     8  import (
     9  	"bufio"
    10  	"bytes"
    11  	"crypto/x509"
    12  	"encoding/binary"
    13  	"encoding/hex"
    14  	"errors"
    15  	"io"
    16  	"math/rand"
    17  	"net"
    18  	"net/http"
    19  	"strconv"
    20  	"sync"
    21  	"time"
    22  
    23  	"go.mondoo.com/cnquery/utils/multierr"
    24  	"golang.org/x/crypto/ocsp"
    25  )
    26  
    27  var TLS_VERSIONS = []string{"ssl3", "tls1.0", "tls1.1", "tls1.2", "tls1.3"}
    28  
    29  // ScanConfig allows to tune the TLS scanner
    30  type ScanConfig struct {
    31  	Versions                  []string
    32  	SNIsupported              bool
    33  	FakeSNI                   bool
    34  	SecureClientRenegotiation bool
    35  
    36  	// internal scan fields that users don't configure
    37  	version       string
    38  	ciphersFilter func(string) bool
    39  }
    40  
    41  func DefaultScanConfig() ScanConfig {
    42  	return ScanConfig{
    43  		SNIsupported:              true,
    44  		FakeSNI:                   true,
    45  		SecureClientRenegotiation: true,
    46  	}
    47  }
    48  
    49  // Tester is the test runner and results object for any findings done in a
    50  // session of tests. We re-use it to avoid duplicate requests and optimize
    51  // the overall test run.
    52  type Tester struct {
    53  	Findings   Findings
    54  	sync       sync.Mutex
    55  	proto      string
    56  	target     string
    57  	domainName string
    58  }
    59  
    60  // Findings tracks the current state of tested components and their findings
    61  type Findings struct {
    62  	Versions           map[string]bool
    63  	Ciphers            map[string]bool
    64  	Extensions         map[string]bool
    65  	Errors             []string
    66  	Certificates       []*x509.Certificate
    67  	NonSNIcertificates []*x509.Certificate
    68  	Revocations        map[string]*revocation
    69  }
    70  
    71  type revocation struct {
    72  	At     time.Time
    73  	Via    string
    74  	Reason int
    75  }
    76  
    77  // New creates a new tester object for the given target (via proto, host, port)
    78  //   - the proto, host, and port are used to construct the target for net.Dial
    79  //     example: proto="tcp", host="mondoo.io", port=443
    80  func New(proto string, domainName string, host string, port int) *Tester {
    81  	target := host + ":" + strconv.Itoa(port)
    82  
    83  	return &Tester{
    84  		Findings: Findings{
    85  			Versions:    map[string]bool{},
    86  			Ciphers:     map[string]bool{},
    87  			Extensions:  map[string]bool{},
    88  			Revocations: map[string]*revocation{},
    89  		},
    90  		proto:      proto,
    91  		target:     target,
    92  		domainName: domainName,
    93  	}
    94  }
    95  
    96  // Test runs the TLS/SSL probes for a given scan configuration
    97  //   - versions may contain any supported pre-defined TLS/SSL versions
    98  //     with a complete list found in TLS_VERSIONS. Leave empty to test all.
    99  func (s *Tester) Test(conf ScanConfig) error {
   100  	if len(conf.Versions) == 0 {
   101  		conf.Versions = TLS_VERSIONS
   102  	}
   103  
   104  	workers := sync.WaitGroup{}
   105  	var errs multierr.Errors
   106  
   107  	remainingCiphers := func(cipher string) bool {
   108  		s.sync.Lock()
   109  		defer s.sync.Unlock()
   110  		if v, ok := s.Findings.Ciphers[cipher]; ok && v {
   111  			return false
   112  		}
   113  		return true
   114  	}
   115  	supportedCiphers := func(cipher string) bool {
   116  		s.sync.Lock()
   117  		defer s.sync.Unlock()
   118  		if v, ok := s.Findings.Ciphers[cipher]; ok && v {
   119  			return true
   120  		}
   121  		return false
   122  	}
   123  
   124  	for i := range conf.Versions {
   125  		version := conf.Versions[i]
   126  
   127  		workers.Add(1)
   128  		go func() {
   129  			defer workers.Done()
   130  
   131  			// we don't activate any of the additional tests in the beginning
   132  			// let's find out if we work on this version of TLS/SSL
   133  			curConf := &ScanConfig{
   134  				version:       version,
   135  				ciphersFilter: remainingCiphers,
   136  			}
   137  
   138  			for {
   139  				remaining, err := s.testTLS(s.proto, s.target, curConf)
   140  				if err != nil {
   141  					s.sync.Lock()
   142  					errs.Add(err)
   143  					s.sync.Unlock()
   144  					return
   145  				}
   146  
   147  				if remaining <= 0 {
   148  					break
   149  				}
   150  			}
   151  
   152  			if version == "tls1.2" || version == "tls1.3" {
   153  				if conf.SNIsupported || conf.SecureClientRenegotiation {
   154  					curConf = &ScanConfig{
   155  						version:                   version,
   156  						ciphersFilter:             supportedCiphers,
   157  						SNIsupported:              conf.SNIsupported,
   158  						SecureClientRenegotiation: conf.SecureClientRenegotiation,
   159  					}
   160  					s.testTLS(s.proto, s.target, curConf)
   161  				}
   162  
   163  				if conf.FakeSNI {
   164  					curConf = &ScanConfig{
   165  						version:       version,
   166  						ciphersFilter: supportedCiphers,
   167  						FakeSNI:       conf.FakeSNI,
   168  					}
   169  					s.testTLS(s.proto, s.target, curConf)
   170  				}
   171  			}
   172  		}()
   173  	}
   174  
   175  	workers.Wait()
   176  
   177  	return errs.Deduplicate()
   178  }
   179  
   180  // Attempts to connect to an endpoint with a given version and records
   181  // results in the Tester.
   182  // Returns the number of remaining ciphers to test (if so desired)
   183  // and any potential error
   184  func (s *Tester) testTLS(proto string, target string, conf *ScanConfig) (int, error) {
   185  	conn, err := net.Dial(proto, target)
   186  	if err != nil {
   187  		return 0, multierr.Wrap(err, "failed to connect to target")
   188  	}
   189  	defer conn.Close()
   190  
   191  	msg, cipherCount, err := s.helloTLSMsg(conf)
   192  	if err != nil {
   193  		return 0, err
   194  	}
   195  
   196  	_, err = conn.Write(msg)
   197  	if err != nil {
   198  		return 0, multierr.Wrap(err, "failed to send TLS hello")
   199  	}
   200  
   201  	success, err := s.parseHello(conn, conf)
   202  	if err != nil || !success {
   203  		return 0, err
   204  	}
   205  
   206  	return cipherCount - 1, nil
   207  }
   208  
   209  func (s *Tester) addError(msg string) {
   210  	s.sync.Lock()
   211  	s.Findings.Errors = append(s.Findings.Errors, msg)
   212  	s.sync.Unlock()
   213  }
   214  
   215  func (s *Tester) parseAlert(data []byte, conf *ScanConfig) error {
   216  	var severity string
   217  	switch data[0] {
   218  	case '\x01':
   219  		severity = "Warning"
   220  	case '\x02':
   221  		severity = "Fatal"
   222  	default:
   223  		severity = "Unknown"
   224  	}
   225  
   226  	description, ok := ALERT_DESCRIPTIONS[data[1]]
   227  	if !ok {
   228  		description = "cannot find description"
   229  	}
   230  
   231  	switch description {
   232  	case "PROTOCOL_VERSION":
   233  		// here we know the TLS version is not supported
   234  		s.sync.Lock()
   235  		s.Findings.Versions[conf.version] = false
   236  		s.sync.Unlock()
   237  
   238  	case "HANDSHAKE_FAILURE":
   239  		if conf.version == "ssl3" {
   240  			// Note: it's a little fuzzy here, since we don't know if the protocol
   241  			// version is unsupported or just its ciphers. So we check if we found
   242  			// it previously and if so, don't add it to the list of unsupported
   243  			// versions.
   244  			if _, ok := s.Findings.Versions["ssl3"]; !ok {
   245  				s.sync.Lock()
   246  				s.Findings.Versions["ssl3"] = false
   247  				s.sync.Unlock()
   248  			}
   249  		}
   250  
   251  		names := cipherNames(conf.version, conf.ciphersFilter)
   252  		for i := range names {
   253  			name := names[i]
   254  			if _, ok := s.Findings.Ciphers[name]; !ok {
   255  				s.sync.Lock()
   256  				s.Findings.Ciphers[name] = false
   257  				s.sync.Unlock()
   258  			}
   259  		}
   260  
   261  	default:
   262  		s.addError("failed to connect via " + conf.version + ": " + severity + " - " + description)
   263  	}
   264  
   265  	return nil
   266  }
   267  
   268  func (s *Tester) parseServerHello(data []byte, version string, conf *ScanConfig) error {
   269  	idx := 0
   270  
   271  	idx += 2 + 32
   272  	// handshake tls version (2), which we don't need yet (we will look at it in the extension if necessary)
   273  	// random (32), which we don't need
   274  
   275  	// we don't need the session ID
   276  	sessionIDlen := byte1int(data[idx])
   277  	idx += 1
   278  	idx += sessionIDlen
   279  
   280  	cipher, cipherOK := ALL_CIPHERS[string(data[idx:idx+2])]
   281  	idx += 2
   282  
   283  	// TLS 1.3 pretends to be TLS 1.2 in the preceding headers for
   284  	// compatibility. To correctly identify it, we have to look at
   285  	// any Supported Versions extensions that the server sent us.
   286  
   287  	// compression method (which should be set to null)
   288  	idx += 1
   289  
   290  	// no extensions found
   291  	var allExtLen int
   292  	if len(data) >= idx+2 {
   293  		allExtLen = bytes2int(data[idx : idx+2])
   294  		idx += 2
   295  	}
   296  
   297  	for allExtLen > 0 && idx < len(data) {
   298  		extType := string(data[idx : idx+2])
   299  		extLen := bytes2int(data[idx+2 : idx+4])
   300  		extData := string(data[idx+4 : idx+4+extLen])
   301  
   302  		allExtLen -= 4 + extLen
   303  		idx += 4 + extLen
   304  
   305  		switch extType {
   306  		case EXTENSION_SupportedVersions:
   307  			if v, ok := VERSIONS_LOOKUP[extData]; ok {
   308  				version = v
   309  			} else {
   310  				s.Findings.Errors = append(s.Findings.Errors, "Failed to parse supported_versions extension: '"+extData+"'")
   311  			}
   312  		case EXTENSION_RenegotiationInfo:
   313  			s.Findings.Extensions["renegotiation_info"] = true
   314  		}
   315  	}
   316  
   317  	// we have to wait for any changes to the detected version (in the extensions)
   318  	// once done, let's lock it once and write all results
   319  	s.sync.Lock()
   320  	if !cipherOK {
   321  		s.Findings.Ciphers["unknown"] = true
   322  	} else {
   323  		s.Findings.Ciphers[cipher] = true
   324  	}
   325  	s.Findings.Versions[version] = true
   326  	s.sync.Unlock()
   327  
   328  	return nil
   329  }
   330  
   331  func (s *Tester) parseCertificate(data []byte, conf *ScanConfig) error {
   332  	certsLen := bytes3int(data[0:3])
   333  	if len(data) < certsLen+3 {
   334  		return errors.New("malformed certificate response, too little data read from stream to parse certificate")
   335  	}
   336  
   337  	certs := []*x509.Certificate{}
   338  	i := 3
   339  	for i < 3+certsLen {
   340  		certLen := bytes3int(data[i : i+3])
   341  		i += 3
   342  
   343  		rawCert := data[i : i+certLen]
   344  		i += certLen
   345  
   346  		cert, err := x509.ParseCertificate(rawCert)
   347  		if err != nil {
   348  			s.addError(
   349  				multierr.Wrap(err, "failed to parse certificate (x509 parser error)").Error(),
   350  			)
   351  		} else {
   352  			certs = append(certs, cert)
   353  		}
   354  	}
   355  
   356  	// TODO: we are currently overwriting any certs that may have been tested already
   357  	// The assumption is that the same endpoint will always return the same
   358  	// certificates no matter what version/configuration is used.
   359  	// This may not be true and in case it isn't improve this code to carefully
   360  	// write new certificates and manage separate certificate chains
   361  	s.sync.Lock()
   362  	if conf.SNIsupported {
   363  		// by default we collect with SNI enabled. If the test is set to test SNI,
   364  		// we actually test the exact opposite, ie what do we get without SNI.
   365  		// Thus, we collect the non-sni certificates here
   366  		s.Findings.NonSNIcertificates = certs
   367  
   368  		if len(certs) != 0 && len(s.Findings.Certificates) != 0 {
   369  			if !bytes.Equal(certs[0].Raw, s.Findings.Certificates[0].Raw) {
   370  				s.Findings.Extensions["server_name"] = true
   371  			}
   372  		}
   373  	} else if conf.FakeSNI {
   374  		if len(certs) != 0 && len(s.Findings.NonSNIcertificates) != 0 {
   375  			if bytes.Equal(certs[0].Raw, s.Findings.NonSNIcertificates[0].Raw) {
   376  				s.Findings.Extensions["fake_server_name"] = true
   377  			}
   378  		}
   379  	} else {
   380  		s.Findings.Certificates = certs
   381  	}
   382  	s.sync.Unlock()
   383  
   384  	for i := 0; i+1 < len(certs); i++ {
   385  		err := s.ocspRequest(certs[i], certs[i+1])
   386  		if err != nil {
   387  			s.addError(err.Error())
   388  		}
   389  	}
   390  
   391  	return nil
   392  }
   393  
   394  // returns true if we are done parsing through handshake responses.
   395  //   - If i'ts a ServerHello, it will check if we have certificates.
   396  //     If we don't, we should read more handshake responses...
   397  //     If we do, we might as well be done at this stage, no need to read more
   398  //   - There are a few other responses that also signal that we are done
   399  //     processing handshake responses, like ServerHelloDone or Finished
   400  func (s *Tester) parseHandshake(data []byte, version string, conf *ScanConfig) (bool, error) {
   401  	handshakeType := data[0]
   402  	handshakeLen := bytes3int(data[1:4])
   403  
   404  	switch handshakeType {
   405  	case HANDSHAKE_TYPE_ServerHello:
   406  		err := s.parseServerHello(data[4:4+handshakeLen], version, conf)
   407  		return false, err
   408  	case HANDSHAKE_TYPE_Certificate:
   409  		return true, s.parseCertificate(data[4:4+handshakeLen], conf)
   410  	case HANDSHAKE_TYPE_ServerKeyExchange:
   411  		return false, nil
   412  	case HANDSHAKE_TYPE_ServerHelloDone:
   413  		return true, nil
   414  	case HANDSHAKE_TYPE_Finished:
   415  		return true, nil
   416  	default:
   417  		typ := "0x" + hex.EncodeToString([]byte{handshakeType})
   418  		s.addError("Unhandled TLS/SSL handshake: '" + typ + "'")
   419  		return false, nil
   420  	}
   421  }
   422  
   423  // returns:
   424  //
   425  //	true if the handshake was successful, false otherwise
   426  func (s *Tester) parseHello(conn net.Conn, conf *ScanConfig) (bool, error) {
   427  	reader := bufio.NewReader(conn)
   428  	header := make([]byte, 5)
   429  	var success bool
   430  	var done bool
   431  
   432  	for !done {
   433  		_, err := io.ReadFull(reader, header)
   434  		if err != nil {
   435  			if err == io.EOF {
   436  				break
   437  			}
   438  			return false, err
   439  		}
   440  
   441  		typ := "0x" + hex.EncodeToString(header[0:1])
   442  		headerVersion := VERSIONS_LOOKUP[string(header[1:3])]
   443  
   444  		msgLen := bytes2int(header[3:5])
   445  		if msgLen == 0 {
   446  			return false, errors.New("No body in TLS/SSL response (type: '" + typ + "')")
   447  		}
   448  		if msgLen > 1<<20 {
   449  			return false, errors.New("TLS/SSL response body is too larget (type: '" + typ + "')")
   450  		}
   451  
   452  		msg := make([]byte, msgLen)
   453  		_, err = io.ReadFull(reader, msg)
   454  		if err != nil {
   455  			return false, multierr.Wrap(err, "Failed to read full TLS/SSL response body (type: '"+typ+"')")
   456  		}
   457  
   458  		switch header[0] {
   459  		case CONTENT_TYPE_Alert:
   460  			// Do not grab the version here, instead use the pre-provided
   461  			// There is a nice edge-case in TLS1.3 which is handled further down,
   462  			// but not required here since we are dealing with an error
   463  			if err := s.parseAlert(msg, conf); err != nil {
   464  				return false, err
   465  			}
   466  
   467  		case CONTENT_TYPE_Handshake:
   468  			handshakeDone, err := s.parseHandshake(msg, headerVersion, conf)
   469  			if err != nil {
   470  				return false, err
   471  			}
   472  			success = true
   473  			done = handshakeDone
   474  
   475  		case CONTENT_TYPE_ChangeCipherSpec:
   476  			// This also means we are done with this stream, since it signals that we
   477  			// are no longer looking at a handshake.
   478  			done = true
   479  
   480  		case CONTENT_TYPE_Application:
   481  			// Definitely don't care about anything past the handshake.
   482  			done = true
   483  
   484  		default:
   485  			s.addError("Unhandled TLS/SSL response (received '" + typ + "')")
   486  		}
   487  	}
   488  
   489  	return success, nil
   490  }
   491  
   492  func filterCipherMsg(org map[string]string, f func(cipher string) bool) ([]byte, int) {
   493  	var res bytes.Buffer
   494  	var n int
   495  	for k, v := range org {
   496  		if f(v) {
   497  			res.WriteString(k)
   498  			n++
   499  		}
   500  	}
   501  	return res.Bytes(), n
   502  }
   503  
   504  func filterCipherNames(org map[string]string, f func(cipher string) bool) []string {
   505  	var res []string
   506  	for _, v := range org {
   507  		if f(v) {
   508  			res = append(res, v)
   509  		}
   510  	}
   511  	return res
   512  }
   513  
   514  func cipherNames(version string, filter func(cipher string) bool) []string {
   515  	switch version {
   516  	case "ssl3":
   517  		regular := filterCipherNames(SSL3_CIPHERS, filter)
   518  		fips := filterCipherNames(SSL_FIPS_CIPHERS, filter)
   519  		return append(regular, fips...)
   520  	case "tls1.0", "tls1.1", "tls1.2":
   521  		return filterCipherNames(TLS_CIPHERS, filter)
   522  	case "tls1.3":
   523  		return filterCipherNames(TLS13_CIPHERS, filter)
   524  	default:
   525  		return []string{}
   526  	}
   527  }
   528  
   529  func writeExtension(buf *bytes.Buffer, typ string, body []byte) {
   530  	buf.WriteString(typ)
   531  	buf.Write(int2bytes(len(body)))
   532  	buf.Write(body)
   533  }
   534  
   535  func sniMsg(domainName string) []byte {
   536  	l := len(domainName)
   537  	var res bytes.Buffer
   538  
   539  	res.Write(int2bytes(l + 3)) // server name list length
   540  	res.WriteByte('\x00')       // name type: host name
   541  	res.Write(int2bytes(l))     // name length
   542  	res.WriteString(domainName) // name
   543  
   544  	return res.Bytes()
   545  }
   546  
   547  func (s *Tester) helloTLSMsg(conf *ScanConfig) ([]byte, int, error) {
   548  	var ciphers []byte
   549  	var cipherCount int
   550  
   551  	var extensions bytes.Buffer
   552  
   553  	if conf.version != "ssl3" {
   554  		domainName := s.domainName
   555  		if conf.SNIsupported {
   556  			// don't write an SNI and see if we get a different certificates
   557  			domainName = ""
   558  		} else if conf.FakeSNI {
   559  			// give it a fake name
   560  			domainName = strconv.FormatUint(rand.Uint64(), 10) + ".com"
   561  		}
   562  
   563  		if domainName != "" {
   564  			writeExtension(&extensions, EXTENSION_ServerName, sniMsg(domainName))
   565  		}
   566  
   567  		// add signature_algorithms
   568  		extensions.WriteString("\x00\x0d\x00\x14\x00\x12\x04\x03\x08\x04\x04\x01\x05\x03\x08\x05\x05\x01\x08\x06\x06\x01\x02\x01")
   569  	}
   570  
   571  	if conf.version == "tls1.2" || conf.version == "tls1.3" {
   572  		// Renegotiation info
   573  		// https://datatracker.ietf.org/doc/html/rfc5746
   574  		// - we leave the body empty, we only need a response to the request
   575  		// - the body has 1 byte containing the length of the extension (which is 0)
   576  		if conf.SecureClientRenegotiation {
   577  			writeExtension(&extensions, EXTENSION_RenegotiationInfo, []byte("\x00"))
   578  		}
   579  	}
   580  
   581  	switch conf.version {
   582  	case "ssl3":
   583  		regular, n1 := filterCipherMsg(SSL3_CIPHERS, conf.ciphersFilter)
   584  		fips, n2 := filterCipherMsg(SSL_FIPS_CIPHERS, conf.ciphersFilter)
   585  		ciphers = append(regular, fips...)
   586  		cipherCount = n1 + n2
   587  
   588  	case "tls1.0", "tls1.1", "tls1.2":
   589  		org, n1 := filterCipherMsg(TLS10_CIPHERS, conf.ciphersFilter)
   590  		tls, n2 := filterCipherMsg(TLS_CIPHERS, conf.ciphersFilter)
   591  		ciphers = append(org, tls...)
   592  		cipherCount = n1 + n2
   593  
   594  		// add heartbeat
   595  		extensions.WriteString("\x00\x0f\x00\x01\x01")
   596  		// add ec_points_format
   597  		extensions.WriteString("\x00\x0b\x00\x02\x01\x00")
   598  		// add elliptic_curve
   599  		extensions.WriteString("\x00\x0a\x00\x0a\x00\x08\xfa\xfa\x00\x1d\x00\x17\x00\x18")
   600  
   601  	case "tls1.3":
   602  		org, n1 := filterCipherMsg(TLS10_CIPHERS, conf.ciphersFilter)
   603  		tls, n2 := filterCipherMsg(TLS_CIPHERS, conf.ciphersFilter)
   604  		tls13, n3 := filterCipherMsg(TLS13_CIPHERS, conf.ciphersFilter)
   605  		ciphers = append(org, tls...)
   606  		ciphers = append(ciphers, tls13...)
   607  		cipherCount = n1 + n2 + n3
   608  
   609  		// TLSv1.3 Supported Versions extension
   610  		extensions.WriteString("\x00\x2b\x00\x03\x02\x03\x04")
   611  		// add supported groups extension
   612  		extensions.WriteString("\x00\x0a\x00\x08\x00\x06\x00\x1d\x00\x17\x00\x18")
   613  
   614  		// This is a pre-generated public/private key pair using the x25519 curve:
   615  		// It was generated from the command line with:
   616  		//
   617  		// > openssl-1.1.1e/apps/openssl genpkey -algorithm x25519 > pkey
   618  		// > openssl-1.1.1e/apps/openssl pkey -noout -text < pkey
   619  		// priv:
   620  		//     30:90:f3:89:f4:9e:52:59:3c:ba:e9:f4:78:84:a0:
   621  		//     23:86:73:5e:f5:c9:46:6c:3a:c3:4e:ec:56:57:81:
   622  		//     5d:62
   623  		// pub:
   624  		//     e7:08:71:36:d0:81:e0:16:19:3a:cb:67:ca:b8:28:
   625  		//     d9:45:92:16:ff:36:63:0d:0d:5a:3d:9d:47:ce:3e:
   626  		//     cd:7e
   627  
   628  		publicKey := "\xe7\x08\x71\x36\xd0\x81\xe0\x16\x19\x3a\xcb\x67\xca\xb8\x28\xd9\x45\x92\x16\xff\x36\x63\x0d\x0d\x5a\x3d\x9d\x47\xce\x3e\xcd\x7e"
   629  		extensions.WriteString("\x00\x33\x00\x26\x00\x24\x00\x1d\x00\x20")
   630  		extensions.WriteString(publicKey)
   631  
   632  	default:
   633  		return nil, 0, errors.New("unsupported TLS/SSL version: " + conf.version)
   634  	}
   635  
   636  	return constructTLSHello(conf.version, ciphers, extensions.Bytes()), cipherCount, nil
   637  }
   638  
   639  // OCSP:
   640  // https://datatracker.ietf.org/doc/html/rfc6960
   641  // https://datatracker.ietf.org/doc/html/rfc2560
   642  
   643  func (s *Tester) ocspRequest(cert *x509.Certificate, issuer *x509.Certificate) error {
   644  	if len(cert.OCSPServer) == 0 {
   645  		return errors.New("no OCSP server specified for revocation check, skipping it")
   646  	}
   647  
   648  	server := cert.OCSPServer[0]
   649  
   650  	req, err := ocsp.CreateRequest(cert, issuer, &ocsp.RequestOptions{})
   651  	if err != nil {
   652  		return multierr.Wrap(err, "failed to create OCSP request")
   653  	}
   654  
   655  	reqBody := bytes.NewBuffer(req)
   656  	res, err := http.Post(server, "application/ocsp-request", reqBody)
   657  	if err != nil {
   658  		return multierr.Wrap(err, "failed to post OCSP request")
   659  	}
   660  
   661  	if res.StatusCode != 200 {
   662  		return errors.New("OCSP request returned " + res.Status)
   663  	}
   664  	resp, err := io.ReadAll(res.Body)
   665  	if err != nil {
   666  		return multierr.Wrap(err, "failed to read OCSP response")
   667  	}
   668  	ocspRes, err := ocsp.ParseResponseForCert(resp, cert, issuer)
   669  	if err != nil {
   670  		return multierr.Wrap(err, "failed to parse OCSP response")
   671  	}
   672  
   673  	s.sync.Lock()
   674  	if ocspRes.RevokedAt.IsZero() {
   675  		s.Findings.Revocations[string(cert.Signature)] = nil
   676  	} else {
   677  		s.Findings.Revocations[string(cert.Signature)] = &revocation{
   678  			At:     ocspRes.RevokedAt,
   679  			Via:    server,
   680  			Reason: ocspRes.RevocationReason,
   681  		}
   682  	}
   683  	s.sync.Unlock()
   684  
   685  	return nil
   686  }
   687  
   688  func int1byte(i int) []byte {
   689  	res := make([]byte, 2)
   690  	binary.BigEndian.PutUint16(res, uint16(i))
   691  	return res[1:]
   692  }
   693  
   694  func int2bytes(i int) []byte {
   695  	res := make([]byte, 2)
   696  	binary.BigEndian.PutUint16(res, uint16(i))
   697  	return res
   698  }
   699  
   700  func int3bytes(i int) []byte {
   701  	res := make([]byte, 4)
   702  	binary.BigEndian.PutUint32(res, uint32(i))
   703  	return res[1:]
   704  }
   705  
   706  func byte1int(b byte) int {
   707  	return int(binary.BigEndian.Uint16([]byte{0x00, b}))
   708  }
   709  
   710  func bytes2int(b []byte) int {
   711  	return int(binary.BigEndian.Uint16(b))
   712  }
   713  
   714  func bytes3int(b []byte) int {
   715  	return int(binary.BigEndian.Uint32(append([]byte{0x00}, b...)))
   716  }
   717  
   718  func constructTLSHello(version string, ciphers []byte, extensions []byte) []byte {
   719  	sessionID := ""
   720  	compressions := "\x00"
   721  
   722  	var content bytes.Buffer
   723  	content.WriteString(VERSIONS[version])
   724  
   725  	rnd := make([]byte, 8)
   726  	binary.BigEndian.PutUint64(rnd, rand.Uint64())
   727  	content.Write(rnd)
   728  	binary.BigEndian.PutUint64(rnd, rand.Uint64())
   729  	content.Write(rnd)
   730  	binary.BigEndian.PutUint64(rnd, rand.Uint64())
   731  	content.Write(rnd)
   732  	binary.BigEndian.PutUint64(rnd, rand.Uint64())
   733  	content.Write(rnd)
   734  
   735  	content.Write(int1byte(len(sessionID)))
   736  	content.WriteString(sessionID)
   737  
   738  	content.Write(int2bytes(len(ciphers)))
   739  	content.Write(ciphers)
   740  
   741  	content.Write(int1byte(len(compressions)))
   742  	content.WriteString(compressions)
   743  
   744  	content.Write(int2bytes(len(extensions)))
   745  	content.Write(extensions)
   746  
   747  	c := content.Bytes()
   748  
   749  	core := []byte{HANDSHAKE_TYPE_ClientHello}
   750  	core = append(core, int3bytes(len(c))...)
   751  	core = append(core, c...)
   752  
   753  	return constructTLSMsg(CONTENT_TYPE_Handshake, core, []byte(VERSIONS[version]))
   754  }
   755  
   756  func constructTLSMsg(contentType byte, content []byte, version []byte) []byte {
   757  	var res bytes.Buffer
   758  	res.WriteByte(contentType)
   759  	res.Write(version)
   760  	res.Write(int2bytes(len(content)))
   761  	res.Write(content)
   762  	return res.Bytes()
   763  }
   764  
   765  var VERSIONS = map[string]string{
   766  	"ssl3":   "\x03\x00",
   767  	"tls1.0": "\x03\x01",
   768  	"tls1.1": "\x03\x02",
   769  	"tls1.2": "\x03\x03",
   770  	// RFC 8446 4.1.2:
   771  	// In TLS 1.3, the client indicates its version preferences in the
   772  	// "supported_versions" extension (Section 4.2.1) and the
   773  	// legacy_version field MUST be set to 0x0303, which is the version
   774  	// number for TLS 1.2.  TLS 1.3 ClientHellos are identified as having
   775  	// a legacy_version of 0x0303 and a supported_versions extension
   776  	// present with 0x0304 as the highest version indicated therein.
   777  	"tls1.3": "\x03\x04",
   778  }
   779  
   780  var (
   781  	VERSIONS_LOOKUP map[string]string
   782  	ALL_CIPHERS     map[string]string
   783  )
   784  
   785  func init() {
   786  	VERSIONS_LOOKUP = make(map[string]string, len(VERSIONS))
   787  	for k, v := range VERSIONS {
   788  		VERSIONS_LOOKUP[v] = k
   789  	}
   790  
   791  	ALL_CIPHERS = make(map[string]string,
   792  		len(SSL2_CIPHERS)+
   793  			len(SSL_FIPS_CIPHERS)+
   794  			len(TLS10_CIPHERS)+
   795  			len(TLS13_CIPHERS)+
   796  			len(TLS_CIPHERS))
   797  
   798  	// Note: overlapping names will be overwritten
   799  	for k, v := range SSL2_CIPHERS {
   800  		ALL_CIPHERS[k] = v
   801  	}
   802  	for k, v := range SSL3_CIPHERS {
   803  		ALL_CIPHERS[k] = v
   804  	}
   805  	for k, v := range SSL_FIPS_CIPHERS {
   806  		ALL_CIPHERS[k] = v
   807  	}
   808  	for k, v := range TLS10_CIPHERS {
   809  		ALL_CIPHERS[k] = v
   810  	}
   811  	for k, v := range TLS13_CIPHERS {
   812  		ALL_CIPHERS[k] = v
   813  	}
   814  	for k, v := range TLS_CIPHERS {
   815  		ALL_CIPHERS[k] = v
   816  	}
   817  }
   818  
   819  const (
   820  	CONTENT_TYPE_ChangeCipherSpec byte = '\x14'
   821  	CONTENT_TYPE_Alert            byte = '\x15'
   822  	CONTENT_TYPE_Handshake        byte = '\x16'
   823  	CONTENT_TYPE_Application      byte = '\x17'
   824  	CONTENT_TYPE_Heartbeat        byte = '\x18'
   825  
   826  	HANDSHAKE_TYPE_HelloRequest       byte = '\x00'
   827  	HANDSHAKE_TYPE_ClientHello        byte = '\x01'
   828  	HANDSHAKE_TYPE_ServerHello        byte = '\x02'
   829  	HANDSHAKE_TYPE_NewSessionTicket   byte = '\x04'
   830  	HANDSHAKE_TYPE_Certificate        byte = '\x0b'
   831  	HANDSHAKE_TYPE_ServerKeyExchange  byte = '\x0c'
   832  	HANDSHAKE_TYPE_CertificateRequest byte = '\x0d'
   833  	HANDSHAKE_TYPE_ServerHelloDone    byte = '\x0e'
   834  	HANDSHAKE_TYPE_CertificateVerify  byte = '\x0f'
   835  	HANDSHAKE_TYPE_ClientKeyExchange  byte = '\x10'
   836  	HANDSHAKE_TYPE_Finished           byte = '\x14'
   837  
   838  	EXTENSION_ServerName        string = "\x00\x00"
   839  	EXTENSION_SupportedVersions string = "\x00\x2b"
   840  	EXTENSION_RenegotiationInfo string = "\xff\x01"
   841  )
   842  
   843  // https://tools.ietf.org/html/rfc5246#appendix-A.3
   844  // https://tools.ietf.org/html/rfc8446#appendix-B.2
   845  var ALERT_DESCRIPTIONS = map[byte]string{
   846  	'\x00': "CLOSE_NOTIFY",
   847  	'\x0A': "UNEXPECTED_MESSAGE",
   848  	'\x14': "BAD_RECORD_MAC",
   849  	'\x15': "DECRYPTION_FAILED_RESERVED",
   850  	'\x16': "RECORD_OVERFLOW",
   851  	'\x1E': "DECOMPRESSION_FAILURE",
   852  	'\x28': "HANDSHAKE_FAILURE",
   853  	'\x29': "NO_CERTIFICATE_RESERVED",
   854  	'\x2A': "BAD_CERTIFICATE",
   855  	'\x2B': "UNSUPPORTED_CERTIFICATE",
   856  	'\x2C': "CERTIFICATE_REVOKED",
   857  	'\x2D': "CERTIFICATE_EXPIRED",
   858  	'\x2E': "CERTIFICATE_UNKNOWN",
   859  	'\x2F': "ILLEGAL_PARAMETER",
   860  	'\x30': "UNKNOWN_CA",
   861  	'\x31': "ACCESS_DENIED",
   862  	'\x32': "DECODE_ERROR",
   863  	'\x33': "DECRYPT_ERROR",
   864  	'\x3C': "EXPORT_RESTRICTION_RESERVED",
   865  	'\x46': "PROTOCOL_VERSION",
   866  	'\x47': "INSUFFICIENT_SECURITY",
   867  	'\x50': "INTERNAL_ERROR",
   868  	'\x56': "INAPPROPRIATE_FALLBACK",
   869  	'\x5A': "USER_CANCELED",
   870  	'\x64': "NO_RENEGOTIATION_RESERVED",
   871  	'\x6D': "MISSING_EXTENSION",
   872  	'\x6E': "UNSUPPORTED_EXTENSION",
   873  	'\x6F': "CERTIFICATE_UNOBTAINABLE_RESERVED",
   874  	'\x70': "UNRECOGNIZED_NAME",
   875  	'\x71': "BAD_CERTIFICATE_STATUS_RESPONSE",
   876  	'\x72': "BAD_CERTIFICATE_HASH_VALUE_RESERVED",
   877  	'\x73': "UNKNOWN_PSK_IDENTITY",
   878  	'\x74': "CERTIFICATE_REQUIRED",
   879  	'\x78': "NO_APPLICATION_PROTOCOL",
   880  }