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

     1  // Copyright (c) Mondoo, Inc.
     2  // SPDX-License-Identifier: BUSL-1.1
     3  
     4  package dnsshake
     5  
     6  import (
     7  	"crypto/rsa"
     8  	"crypto/x509"
     9  	"encoding/base64"
    10  	"errors"
    11  	"io"
    12  	"mime/quotedprintable"
    13  	"strings"
    14  )
    15  
    16  // DkimPublicKeyRepresentation represents a parsed version of public key record
    17  // see https://datatracker.ietf.org/doc/html/rfc6376
    18  type DkimPublicKeyRepresentation struct {
    19  	// Version of the DKIM key record (plain-text; RECOMMENDED, default is "DKIM1")
    20  	Version string
    21  	// Acceptable hash algorithms (plain-text; OPTIONAL, defaults to allowing all algorithms)
    22  	HashAlgorithms []string
    23  	// Key type (plain-text; OPTIONAL, default is "rsa")
    24  	KeyType string
    25  	// Notes that might be of interest to a human (qp-section; OPTIONAL, default is empty)
    26  	Notes string
    27  	// Public-key data (base64; REQUIRED)
    28  	PublicKeyData string
    29  	// Service Type (plain-text; OPTIONAL; default is "*")
    30  	ServiceType []string
    31  	// Flags, represented as a colon-separated list of names (plain-text; OPTIONAL, default is no flags set)
    32  	Flags []string
    33  }
    34  
    35  func (pkr *DkimPublicKeyRepresentation) Valid() (bool, []string, []string) {
    36  	errorMsg := []string{}
    37  	warningMsg := []string{}
    38  	if pkr.Version != "" && pkr.Version != "DKIM1" {
    39  		errorMsg = append(errorMsg, "If version is specified, this tag MUST be set to \"DKIM1\"")
    40  	}
    41  
    42  	if pkr.KeyType != "" && pkr.KeyType != "rsa" {
    43  		// according to RFC, wrong types should be ignored but since it would not match with the public key
    44  		// we throw an error here
    45  		errorMsg = append(errorMsg, "Unrecognized key types")
    46  	}
    47  
    48  	if pkr.PublicKeyData == "" {
    49  		// NOTE: empty value represents a revoked key, we could argue that it is a warning
    50  		errorMsg = append(errorMsg, "public key has been revoked")
    51  	}
    52  
    53  	if pkr.PublicKeyData != "" {
    54  		_, err := pkr.PublicKey()
    55  		if err != nil {
    56  			errorMsg = append(errorMsg, "unable to parse public key")
    57  		}
    58  	}
    59  
    60  	// TODO: we may want to add warning checks for service type and flags values
    61  	return len(errorMsg) == 0, errorMsg, warningMsg
    62  }
    63  
    64  func (pkr *DkimPublicKeyRepresentation) PublicKey() (*rsa.PublicKey, error) {
    65  	if pkr.PublicKeyData == "" {
    66  		return nil, errors.New("public key has been revoked")
    67  	}
    68  	pem64, err := base64.StdEncoding.DecodeString(pkr.PublicKeyData)
    69  	if err != nil {
    70  		return nil, errors.New("could not parse public key data")
    71  	}
    72  
    73  	pk, _ := x509.ParsePKIXPublicKey(pem64)
    74  	if pk, ok := pk.(*rsa.PublicKey); ok {
    75  		return pk, nil
    76  	}
    77  	return nil, errors.New("invalid rsa key")
    78  }
    79  
    80  // NewDkimPublicKeyRepresentation parses DNS DKIM record
    81  // https://datatracker.ietf.org/doc/html/rfc6376#section-3.6.1
    82  func NewDkimPublicKeyRepresentation(dkimRecord string) (*DkimPublicKeyRepresentation, error) {
    83  	pkr := &DkimPublicKeyRepresentation{}
    84  	p := strings.Split(dkimRecord, ";")
    85  	for i, data := range p {
    86  		keyVal := strings.SplitN(data, "=", 2)
    87  		key := keyVal[0]
    88  		val := ""
    89  		if len(keyVal) > 1 {
    90  			val = strings.TrimSpace(keyVal[1])
    91  		}
    92  		switch strings.ToLower(strings.TrimSpace(key)) {
    93  		case "v":
    94  			// RFC: This tag MUST be the first tag in the record.
    95  			if i != 0 {
    96  				return nil, errors.New("invalid DKIM record")
    97  			}
    98  			pkr.Version = val
    99  		case "h":
   100  			p := strings.Split(strings.ToLower(val), ":")
   101  			for i := range p {
   102  				h := strings.TrimSpace(p[i])
   103  				pkr.HashAlgorithms = append(pkr.HashAlgorithms, h)
   104  			}
   105  		case "k":
   106  			pkr.KeyType = strings.ToLower(val)
   107  		case "n":
   108  			pkr.Notes = val
   109  			// parse quote printable
   110  			qp, err := io.ReadAll(quotedprintable.NewReader(strings.NewReader(val)))
   111  			if err == nil {
   112  				pkr.Notes = string(qp)
   113  			}
   114  		case "p":
   115  			pkr.PublicKeyData = val
   116  		case "s":
   117  			serviceTypes := strings.Split(strings.ToLower(val), ":")
   118  			for i := range serviceTypes {
   119  				pkr.ServiceType = append(pkr.ServiceType, strings.TrimSpace(serviceTypes[i]))
   120  			}
   121  		case "t":
   122  			flags := strings.Split(strings.ToLower(val), ":")
   123  			for i := range flags {
   124  				pkr.Flags = append(pkr.Flags, strings.TrimSpace(flags[i]))
   125  			}
   126  		}
   127  	}
   128  
   129  	return pkr, nil
   130  }