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 }