go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/providers/network/resources/certificates.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 "crypto/x509/pkix" 9 "encoding/hex" 10 "errors" 11 "fmt" 12 "strings" 13 "sync" 14 "time" 15 16 "github.com/rs/zerolog/log" 17 "go.mondoo.com/cnquery/checksums" 18 "go.mondoo.com/cnquery/llx" 19 "go.mondoo.com/cnquery/providers-sdk/v1/plugin" 20 "go.mondoo.com/cnquery/providers/network/resources/certificates" 21 "go.mondoo.com/cnquery/types" 22 ) 23 24 func pkixnameToMql(runtime *plugin.Runtime, name pkix.Name, id string) (*mqlPkixName, error) { 25 names := map[string]interface{}{} 26 for i := range name.Names { 27 key := name.Names[i].Type.String() 28 names[key] = fmt.Sprintf("%v", name.Names[i].Value) 29 } 30 31 extraNames := map[string]interface{}{} 32 for i := range name.ExtraNames { 33 key := name.ExtraNames[i].Type.String() 34 extraNames[key] = fmt.Sprintf("%v", name.ExtraNames[i].Value) 35 } 36 37 r, err := CreateResource(runtime, "pkix.name", map[string]*llx.RawData{ 38 "id": llx.StringData(id), 39 "dn": llx.StringData(name.String()), 40 "serialNumber": llx.StringData(name.SerialNumber), 41 "commonName": llx.StringData(name.CommonName), 42 "country": llx.ArrayData(llx.TArr2Raw(name.Country), types.String), 43 "organization": llx.ArrayData(llx.TArr2Raw(name.Organization), types.String), 44 "organizationalUnit": llx.ArrayData(llx.TArr2Raw(name.OrganizationalUnit), types.String), 45 "locality": llx.ArrayData(llx.TArr2Raw(name.Locality), types.String), 46 "province": llx.ArrayData(llx.TArr2Raw(name.Province), types.String), 47 "streetAddress": llx.ArrayData(llx.TArr2Raw(name.StreetAddress), types.String), 48 "postalCode": llx.ArrayData(llx.TArr2Raw(name.PostalCode), types.String), 49 "names": llx.MapData(names, types.String), 50 "extraNames": llx.MapData(extraNames, types.String), 51 }) 52 if err != nil { 53 return nil, err 54 } 55 return r.(*mqlPkixName), nil 56 } 57 58 func pkixextensionToMql(runtime *plugin.Runtime, ext pkix.Extension, id string) (*mqlPkixExtension, error) { 59 r, err := CreateResource(runtime, "pkix.extension", map[string]*llx.RawData{ 60 "identifier": llx.StringData(id), 61 "critical": llx.BoolData(ext.Critical), 62 "value": llx.StringData(string(ext.Value)), 63 }) 64 if err != nil { 65 return nil, err 66 } 67 return r.(*mqlPkixExtension), nil 68 } 69 70 func (r *mqlCertificates) id() (string, error) { 71 return checksums.New.Add(r.Pem.Data).String(), nil 72 } 73 74 func (r *mqlCertificates) list() ([]interface{}, error) { 75 certs, err := certificates.ParseCertsFromPEM(strings.NewReader(r.Pem.Data)) 76 if err != nil { 77 return nil, errors.New("certificate has invalid pem data: " + err.Error()) 78 } 79 80 return CertificatesToMqlCertificates(r.MqlRuntime, certs) 81 } 82 83 // CertificatesToMqlCertificates takes a collection of x509 certs 84 // and converts it into MQL certificate objects 85 func CertificatesToMqlCertificates(runtime *plugin.Runtime, certs []*x509.Certificate) ([]interface{}, error) { 86 res := []interface{}{} 87 // to create certificate resources 88 for i := range certs { 89 cert := certs[i] 90 91 if cert == nil { 92 continue 93 } 94 95 certdata, err := certificates.EncodeCertAsPEM(cert) 96 if err != nil { 97 return nil, err 98 } 99 100 r, err := CreateResource(runtime, "certificate", map[string]*llx.RawData{ 101 "pem": llx.StringData(string(certdata)), 102 // NOTE: if we do not set the hash here, it will generate the cache content before we can store it 103 // we are using the hashes for the id, therefore it is required during creation 104 "fingerprints": llx.MapData(certificates.Fingerprints(cert), types.String), 105 }) 106 if err != nil { 107 return nil, err 108 } 109 110 c := r.(*mqlCertificate) 111 c.cert = plugin.TValue[*x509.Certificate]{ 112 Data: cert, 113 State: plugin.StateIsSet, 114 } 115 116 res = append(res, c) 117 } 118 return res, nil 119 } 120 121 func (r *mqlCertificate) id() (string, error) { 122 fp := r.GetFingerprints() 123 if fp.Error != nil { 124 return "", fp.Error 125 } 126 x, ok := fp.Data["sha256"] 127 if !ok { 128 return "", errors.New("missing sha256 fingerprints for certificate") 129 } 130 131 return "certificate:" + x.(string), nil 132 } 133 134 type mqlCertificateInternal struct { 135 cert plugin.TValue[*x509.Certificate] 136 allCertFieldsSet bool 137 lock sync.Mutex 138 } 139 140 func (s *mqlCertificate) parse() ([]*x509.Certificate, error) { 141 pem := s.GetPem() 142 if pem.Error != nil { 143 return nil, errors.New("certificate is missing pem data: " + pem.Error.Error()) 144 } 145 146 certs, err := certificates.ParseCertsFromPEM(strings.NewReader(pem.Data)) 147 if err != nil { 148 return nil, errors.New("certificate has invalid pem data: " + err.Error()) 149 } 150 151 return certs, nil 152 } 153 154 func (s *mqlCertificate) getGoCert() error { 155 s.lock.Lock() 156 defer s.lock.Unlock() 157 158 if s.cert.State&plugin.StateIsSet == 0 { 159 certs, err := s.parse() 160 if err != nil { 161 s.cert = plugin.TValue[*x509.Certificate]{State: plugin.StateIsSet, Error: err} 162 return err 163 } 164 165 if len(certs) > 1 { 166 log.Error().Msg("pem for cert contains more than one certificate, ignore additional certificates") 167 } 168 169 s.cert = plugin.TValue[*x509.Certificate]{Data: certs[0], State: plugin.StateIsSet} 170 } 171 172 if !s.allCertFieldsSet { 173 s.allCertFieldsSet = true 174 175 cert := s.cert.Data 176 s.Fingerprints = plugin.TValue[map[string]interface{}]{Data: certificates.Fingerprints(cert), State: plugin.StateIsSet} 177 s.Serial = plugin.TValue[string]{Data: certificates.HexEncodeToHumanString(cert.SerialNumber.Bytes()), State: plugin.StateIsSet} 178 s.SubjectKeyID = plugin.TValue[string]{Data: certificates.HexEncodeToHumanString(cert.SubjectKeyId), State: plugin.StateIsSet} 179 s.AuthorityKeyID = plugin.TValue[string]{Data: certificates.HexEncodeToHumanString(cert.AuthorityKeyId), State: plugin.StateIsSet} 180 s.Version = plugin.TValue[int64]{Data: int64(cert.Version), State: plugin.StateIsSet} 181 s.IsCA = plugin.TValue[bool]{Data: cert.IsCA, State: plugin.StateIsSet} 182 s.NotBefore = plugin.TValue[*time.Time]{Data: &cert.NotBefore, State: plugin.StateIsSet} 183 s.NotAfter = plugin.TValue[*time.Time]{Data: &cert.NotAfter, State: plugin.StateIsSet} 184 diff := cert.NotAfter.Unix() - time.Now().Unix() 185 expiresIn := llx.DurationToTime(diff) 186 s.ExpiresIn = plugin.TValue[*time.Time]{Data: &expiresIn, State: plugin.StateIsSet} 187 s.SigningAlgorithm = plugin.TValue[string]{Data: cert.SignatureAlgorithm.String(), State: plugin.StateIsSet} 188 s.Signature = plugin.TValue[string]{Data: hex.EncodeToString(cert.Signature), State: plugin.StateIsSet} 189 s.CrlDistributionPoints = plugin.TValue[[]interface{}]{Data: llx.TArr2Raw(cert.CRLDistributionPoints), State: plugin.StateIsSet} 190 s.OcspServer = plugin.TValue[[]interface{}]{Data: llx.TArr2Raw(cert.OCSPServer), State: plugin.StateIsSet} 191 s.IssuingCertificateUrl = plugin.TValue[[]interface{}]{Data: llx.TArr2Raw(cert.IssuingCertificateURL), State: plugin.StateIsSet} 192 } 193 194 // in case the cert was already set, use the cached error state 195 return s.cert.Error 196 } 197 198 func (s *mqlCertificate) fingerprints() (map[string]interface{}, error) { 199 return nil, s.getGoCert() 200 } 201 202 func (s *mqlCertificate) serial() (string, error) { 203 // TODO: we may want return bytes and leave the printing to runtime 204 return "", s.getGoCert() 205 } 206 207 func (s *mqlCertificate) subjectKeyID() (string, error) { 208 // TODO: we may want return bytes and leave the printing to runtime 209 return "", s.getGoCert() 210 } 211 212 func (s *mqlCertificate) authorityKeyID() (string, error) { 213 // TODO: we may want return bytes and leave the printing to runtime 214 return "", s.getGoCert() 215 } 216 217 func (s *mqlCertificate) subject() (*mqlPkixName, error) { 218 if err := s.getGoCert(); err != nil { 219 return nil, err 220 } 221 222 fingerprint := hex.EncodeToString(certificates.Sha256Hash(s.cert.Data)) 223 mqlSubject, err := pkixnameToMql(s.MqlRuntime, s.cert.Data.Subject, fingerprint+":subject") 224 if err != nil { 225 return nil, err 226 } 227 return mqlSubject, nil 228 } 229 230 func (s *mqlCertificate) issuer() (*mqlPkixName, error) { 231 if err := s.getGoCert(); err != nil { 232 return nil, err 233 } 234 235 fingerprint := hex.EncodeToString(certificates.Sha256Hash(s.cert.Data)) 236 mqlIssuer, err := pkixnameToMql(s.MqlRuntime, s.cert.Data.Issuer, fingerprint+":issuer") 237 if err != nil { 238 return nil, err 239 } 240 return mqlIssuer, nil 241 } 242 243 func (s *mqlCertificate) version() (int64, error) { 244 return 0, s.getGoCert() 245 } 246 247 func (s *mqlCertificate) isCA() (bool, error) { 248 return false, s.getGoCert() 249 } 250 251 func (s *mqlCertificate) notBefore() (*time.Time, error) { 252 return nil, s.getGoCert() 253 } 254 255 func (s *mqlCertificate) notAfter() (*time.Time, error) { 256 return nil, s.getGoCert() 257 } 258 259 func (s *mqlCertificate) expiresIn() (*time.Time, error) { 260 return nil, s.getGoCert() 261 } 262 263 var keyusageNames = map[x509.KeyUsage]string{ 264 x509.KeyUsageDigitalSignature: "DigitalSignature", 265 x509.KeyUsageContentCommitment: "ContentCommitment", 266 x509.KeyUsageKeyEncipherment: "KeyEncipherment", 267 x509.KeyUsageDataEncipherment: "DataEncipherment", 268 x509.KeyUsageKeyAgreement: "KeyAgreement", 269 x509.KeyUsageCertSign: "CertificateSign", 270 x509.KeyUsageCRLSign: "CRLSign", 271 x509.KeyUsageEncipherOnly: "EncipherOnly", 272 x509.KeyUsageDecipherOnly: "DecipherOnly", 273 } 274 275 func (s *mqlCertificate) keyUsage() ([]interface{}, error) { 276 if err := s.getGoCert(); err != nil { 277 return nil, err 278 } 279 280 res := []interface{}{} 281 for k := range keyusageNames { 282 if s.cert.Data.KeyUsage&k != 0 { 283 res = append(res, keyusageNames[k]) 284 } 285 } 286 287 return res, nil 288 } 289 290 var extendendkeyusageNames = map[x509.ExtKeyUsage]string{ 291 x509.ExtKeyUsageAny: "Any", 292 x509.ExtKeyUsageServerAuth: "ServerAuth", 293 x509.ExtKeyUsageClientAuth: "ClientAuth", 294 x509.ExtKeyUsageCodeSigning: "CodeSigning", 295 x509.ExtKeyUsageEmailProtection: "EmailProtection", 296 x509.ExtKeyUsageIPSECEndSystem: "IPSECEndSystem", 297 x509.ExtKeyUsageIPSECTunnel: "IPSECTunnel", 298 x509.ExtKeyUsageIPSECUser: "IPSECUser", 299 x509.ExtKeyUsageTimeStamping: "TimeStamping", 300 x509.ExtKeyUsageOCSPSigning: "OCSPSigning", 301 x509.ExtKeyUsageMicrosoftServerGatedCrypto: "MicrosoftServerGatedCrypto", 302 x509.ExtKeyUsageNetscapeServerGatedCrypto: "NetscapeServerGatedCrypto", 303 x509.ExtKeyUsageMicrosoftCommercialCodeSigning: "MicrosoftCommercialCodeSigning", 304 x509.ExtKeyUsageMicrosoftKernelCodeSigning: "MicrosoftKernelCodeSigning", 305 } 306 307 func (s *mqlCertificate) extendedKeyUsage() ([]interface{}, error) { 308 if err := s.getGoCert(); err != nil { 309 return nil, err 310 } 311 312 res := []interface{}{} 313 for i := range s.cert.Data.ExtKeyUsage { 314 entry := s.cert.Data.ExtKeyUsage[i] 315 val, ok := extendendkeyusageNames[entry] 316 if !ok { 317 return nil, fmt.Errorf("unknown extended key usage %d", s.cert.Data.KeyUsage) 318 } 319 res = append(res, val) 320 } 321 return res, nil 322 } 323 324 func (s *mqlCertificate) extensions() ([]interface{}, error) { 325 if err := s.getGoCert(); err != nil { 326 return nil, err 327 } 328 329 cert := s.cert.Data 330 res := []interface{}{} 331 fingerprint := hex.EncodeToString(certificates.Sha256Hash(cert)) 332 for i := range cert.Extensions { 333 extension := cert.Extensions[i] 334 ext, err := pkixextensionToMql(s.MqlRuntime, extension, fingerprint+":"+extension.Id.String()) 335 if err != nil { 336 return nil, err 337 } 338 res = append(res, ext) 339 } 340 return res, nil 341 } 342 343 func (s *mqlCertificate) policyIdentifier() ([]interface{}, error) { 344 if err := s.getGoCert(); err != nil { 345 return nil, err 346 } 347 348 cert := s.cert.Data 349 res := []interface{}{} 350 for i := range cert.PolicyIdentifiers { 351 res = append(res, cert.PolicyIdentifiers[i].String()) 352 } 353 return res, nil 354 } 355 356 func (s *mqlCertificate) signingAlgorithm() (string, error) { 357 return "", s.getGoCert() 358 } 359 360 func (s *mqlCertificate) signature() (string, error) { 361 // TODO: return bytes 362 return "", s.getGoCert() 363 } 364 365 func (s *mqlCertificate) crlDistributionPoints() ([]interface{}, error) { 366 return nil, s.getGoCert() 367 } 368 369 func (s *mqlCertificate) ocspServer() ([]interface{}, error) { 370 return nil, s.getGoCert() 371 } 372 373 func (s *mqlCertificate) issuingCertificateUrl() ([]interface{}, error) { 374 return nil, s.getGoCert() 375 } 376 377 func (s *mqlCertificate) isRevoked() (bool, error) { 378 return false, errors.New("unknown revocation status") 379 } 380 381 func (s *mqlCertificate) revokedAt() (*time.Time, error) { 382 return nil, nil 383 } 384 385 func (s *mqlCertificate) isVerified() (bool, error) { 386 return false, nil 387 } 388 389 func (r *mqlPkixName) id() (string, error) { 390 return r.Id.Data, nil 391 } 392 393 func (r *mqlPkixExtension) id() (string, error) { 394 return r.Identifier.Data, nil 395 }