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  }