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 }