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

     1  // Copyright (c) Mondoo, Inc.
     2  // SPDX-License-Identifier: BUSL-1.1
     3  
     4  package resources
     5  
     6  import (
     7  	"crypto/x509"
     8  	"regexp"
     9  	"strconv"
    10  	"sync"
    11  	"time"
    12  
    13  	"github.com/cockroachdb/errors"
    14  	"go.mondoo.com/cnquery/llx"
    15  	"go.mondoo.com/cnquery/providers-sdk/v1/plugin"
    16  	"go.mondoo.com/cnquery/providers/core/resources/regex"
    17  	"go.mondoo.com/cnquery/providers/network/connection"
    18  	"go.mondoo.com/cnquery/providers/network/resources/certificates"
    19  	"go.mondoo.com/cnquery/providers/network/resources/tlsshake"
    20  	"go.mondoo.com/cnquery/types"
    21  )
    22  
    23  var reTarget = regexp.MustCompile("([^/:]+?)(:\\d+)?$")
    24  
    25  var rexUrlDomain = regexp.MustCompile(regex.UrlDomain)
    26  
    27  func initTls(runtime *plugin.Runtime, args map[string]*llx.RawData) (map[string]*llx.RawData, plugin.Resource, error) {
    28  	// if the socket is set already, we have nothing else to do
    29  	if _, ok := args["socket"]; ok {
    30  		return args, nil, nil
    31  	}
    32  
    33  	conn := runtime.Connection.(*connection.HostConnection)
    34  	if conn.Conf.Port == 0 {
    35  		conn.Conf.Port = 443
    36  	}
    37  
    38  	if target, ok := args["target"]; ok {
    39  		m := reTarget.FindStringSubmatch(target.Value.(string))
    40  		if len(m) == 0 {
    41  			return nil, nil, errors.New("target must be provided in the form of: tcp://target:port, udp://target:port, or target:port (defaults to tcp)")
    42  		}
    43  
    44  		proto := "tcp"
    45  
    46  		var port int64 = 443
    47  		if len(m[2]) != 0 {
    48  			rawPort, err := strconv.ParseUint(m[2][1:], 10, 64)
    49  			if err != nil {
    50  				return nil, nil, errors.New("failed to parse port: " + m[2])
    51  			}
    52  			port = int64(rawPort)
    53  		}
    54  
    55  		address := m[1]
    56  		domainName := ""
    57  		if rexUrlDomain.MatchString(address) {
    58  			domainName = address
    59  		}
    60  
    61  		socket, err := CreateResource(runtime, "socket", map[string]*llx.RawData{
    62  			"protocol": llx.StringData(proto),
    63  			"port":     llx.IntData(port),
    64  			"address":  llx.StringData(address),
    65  		})
    66  		if err != nil {
    67  			return nil, nil, err
    68  		}
    69  
    70  		args["socket"] = llx.ResourceData(socket, "socket")
    71  		args["domainName"] = llx.StringData(domainName)
    72  		delete(args, "target")
    73  
    74  	} else {
    75  		socket, err := CreateResource(runtime, "socket", map[string]*llx.RawData{
    76  			"protocol": llx.StringData("tcp"),
    77  			"port":     llx.IntData(int64(conn.Conf.Port)),
    78  			"address":  llx.StringData(conn.Conf.Host),
    79  		})
    80  		if err != nil {
    81  			return nil, nil, err
    82  		}
    83  
    84  		args["socket"] = llx.ResourceData(socket, "socket")
    85  		args["domainName"] = llx.StringData(conn.Conf.Host)
    86  	}
    87  
    88  	return args, nil, nil
    89  }
    90  
    91  type mqlTlsInternal struct {
    92  	lock sync.Mutex
    93  }
    94  
    95  func (s *mqlTls) id() (string, error) {
    96  	return "tls+" + s.Socket.Data.__id, nil
    97  }
    98  
    99  func parseCertificates(runtime *plugin.Runtime, domainName string, findings *tlsshake.Findings, certificateList []*x509.Certificate) ([]interface{}, error) {
   100  	res := make([]interface{}, len(certificateList))
   101  
   102  	verified := false
   103  	if len(certificateList) != 0 {
   104  		intermediates := x509.NewCertPool()
   105  		for i := 1; i < len(certificateList); i++ {
   106  			intermediates.AddCert(certificateList[i])
   107  		}
   108  
   109  		verifyCerts, err := certificateList[0].Verify(x509.VerifyOptions{
   110  			DNSName:       domainName,
   111  			Intermediates: intermediates,
   112  		})
   113  		if err != nil {
   114  			findings.Errors = append(findings.Errors, "Failed to verify certificate chain for "+certificateList[0].Subject.String())
   115  		}
   116  
   117  		if len(verifyCerts) != 0 {
   118  			verified = verifyCerts[0][0].Equal(certificateList[0])
   119  		}
   120  	}
   121  
   122  	for i := range certificateList {
   123  		cert := certificateList[i]
   124  
   125  		var isRevoked bool
   126  		var revokedAt time.Time
   127  		revocation, ok := findings.Revocations[string(cert.Signature)]
   128  		if ok {
   129  			if revocation == nil {
   130  				isRevoked = false
   131  				revokedAt = llx.NeverFutureTime
   132  			} else {
   133  				isRevoked = true
   134  				revokedAt = revocation.At
   135  			}
   136  		}
   137  
   138  		pem, err := certificates.EncodeCertAsPEM(cert)
   139  		if err != nil {
   140  			return nil, err
   141  		}
   142  
   143  		raw, err := CreateResource(runtime, "certificate", map[string]*llx.RawData{
   144  			"pem": llx.StringData(string(pem)),
   145  			// NOTE: if we do not set the hash here, it will generate the cache content before we can store it
   146  			// we are using the hashs for the id, therefore it is required during creation
   147  			"fingerprints": llx.MapData(certificates.Fingerprints(cert), types.String),
   148  			"isRevoked":    llx.BoolData(isRevoked),
   149  			"revokedAt":    llx.TimeData(revokedAt),
   150  			"isVerified":   llx.BoolData(verified),
   151  		})
   152  		if err != nil {
   153  			return nil, err
   154  		}
   155  
   156  		// store parsed object with resource
   157  		mqlCert := raw.(*mqlCertificate)
   158  		mqlCert.cert = plugin.TValue[*x509.Certificate]{Data: cert, State: plugin.StateIsSet}
   159  
   160  		res[i] = mqlCert
   161  	}
   162  
   163  	return res, nil
   164  }
   165  
   166  func (s *mqlTls) params(socket *mqlSocket, domainName string) (map[string]interface{}, error) {
   167  	s.lock.Lock()
   168  	defer s.lock.Unlock()
   169  
   170  	host := socket.Address.Data
   171  	port := socket.Port.Data
   172  	proto := socket.Protocol.Data
   173  
   174  	tester := tlsshake.New(proto, domainName, host, int(port))
   175  	if err := tester.Test(tlsshake.DefaultScanConfig()); err != nil {
   176  		return nil, err
   177  	}
   178  
   179  	res := map[string]interface{}{}
   180  	findings := tester.Findings
   181  
   182  	lists := map[string][]string{
   183  		"errors": findings.Errors,
   184  	}
   185  	for field, data := range lists {
   186  		v := make([]interface{}, len(data))
   187  		for i := range data {
   188  			v[i] = data[i]
   189  		}
   190  		res[field] = v
   191  	}
   192  
   193  	maps := map[string]map[string]bool{
   194  		"versions":   findings.Versions,
   195  		"ciphers":    findings.Ciphers,
   196  		"extensions": findings.Extensions,
   197  	}
   198  	for field, data := range maps {
   199  		v := make(map[string]interface{}, len(data))
   200  		for k, vv := range data {
   201  			v[k] = vv
   202  		}
   203  		res[field] = v
   204  	}
   205  
   206  	// Create certificates
   207  	certs, err := parseCertificates(s.MqlRuntime, domainName, &findings, findings.Certificates)
   208  	if err != nil {
   209  		s.Certificates = plugin.TValue[[]interface{}]{Error: err, State: plugin.StateIsSet}
   210  	} else {
   211  		s.Certificates = plugin.TValue[[]interface{}]{Data: certs, State: plugin.StateIsSet}
   212  	}
   213  
   214  	certs, err = parseCertificates(s.MqlRuntime, domainName, &findings, findings.NonSNIcertificates)
   215  	if err != nil {
   216  		s.NonSniCertificates = plugin.TValue[[]interface{}]{Error: err, State: plugin.StateIsSet}
   217  	} else {
   218  		s.NonSniCertificates = plugin.TValue[[]interface{}]{Data: certs, State: plugin.StateIsSet}
   219  	}
   220  
   221  	return res, nil
   222  }
   223  
   224  func (s *mqlTls) versions(params interface{}) ([]interface{}, error) {
   225  	paramsM, ok := params.(map[string]interface{})
   226  	if !ok {
   227  		return []interface{}{}, nil
   228  	}
   229  
   230  	raw, ok := paramsM["versions"]
   231  	if !ok {
   232  		return []interface{}{}, nil
   233  	}
   234  
   235  	data := raw.(map[string]interface{})
   236  	res := []interface{}{}
   237  	for k, v := range data {
   238  		if v.(bool) {
   239  			res = append(res, k)
   240  		}
   241  	}
   242  
   243  	return res, nil
   244  }
   245  
   246  func (s *mqlTls) ciphers(params interface{}) ([]interface{}, error) {
   247  	paramsM, ok := params.(map[string]interface{})
   248  	if !ok {
   249  		return []interface{}{}, nil
   250  	}
   251  
   252  	raw, ok := paramsM["ciphers"]
   253  	if !ok {
   254  		return []interface{}{}, nil
   255  	}
   256  
   257  	data := raw.(map[string]interface{})
   258  	res := []interface{}{}
   259  	for k, v := range data {
   260  		if v.(bool) {
   261  			res = append(res, k)
   262  		}
   263  	}
   264  
   265  	return res, nil
   266  }
   267  
   268  func (s *mqlTls) extensions(params interface{}) ([]interface{}, error) {
   269  	paramsM, ok := params.(map[string]interface{})
   270  	if !ok {
   271  		return []interface{}{}, nil
   272  	}
   273  
   274  	raw, ok := paramsM["extensions"]
   275  	if !ok {
   276  		return []interface{}{}, nil
   277  	}
   278  
   279  	data := raw.(map[string]interface{})
   280  	res := []interface{}{}
   281  	for k, v := range data {
   282  		if v.(bool) {
   283  			res = append(res, k)
   284  		}
   285  	}
   286  
   287  	return res, nil
   288  }
   289  
   290  func (s *mqlTls) certificates(params interface{}) ([]interface{}, error) {
   291  	// We leverage the fact that params has to be created first, and that params
   292  	// causes this field to be set. If it isn't, then we cannot determine it.
   293  	// (TODO: use the recording data to do this async)
   294  	return nil, nil
   295  }
   296  
   297  func (s *mqlTls) nonSniCertificates(params interface{}) ([]interface{}, error) {
   298  	// We leverage the fact that params has to be created first, and that params
   299  	// causes this field to be set. If it isn't, then we cannot determine it.
   300  	// (TODO: use the recording data to do this async)
   301  	return nil, nil
   302  }