query.go raw

   1  package dkim
   2  
   3  import (
   4  	"crypto"
   5  	"crypto/rsa"
   6  	"crypto/x509"
   7  	"encoding/base64"
   8  	"errors"
   9  	"fmt"
  10  	"net"
  11  	"strings"
  12  
  13  	"golang.org/x/crypto/ed25519"
  14  )
  15  
  16  type verifier interface {
  17  	Public() crypto.PublicKey
  18  	Verify(hash crypto.Hash, hashed []byte, sig []byte) error
  19  }
  20  
  21  type rsaVerifier struct {
  22  	*rsa.PublicKey
  23  }
  24  
  25  func (v rsaVerifier) Public() crypto.PublicKey {
  26  	return v.PublicKey
  27  }
  28  
  29  func (v rsaVerifier) Verify(hash crypto.Hash, hashed, sig []byte) error {
  30  	return rsa.VerifyPKCS1v15(v.PublicKey, hash, hashed, sig)
  31  }
  32  
  33  type ed25519Verifier struct {
  34  	ed25519.PublicKey
  35  }
  36  
  37  func (v ed25519Verifier) Public() crypto.PublicKey {
  38  	return v.PublicKey
  39  }
  40  
  41  func (v ed25519Verifier) Verify(hash crypto.Hash, hashed, sig []byte) error {
  42  	if !ed25519.Verify(v.PublicKey, hashed, sig) {
  43  		return errors.New("dkim: invalid Ed25519 signature")
  44  	}
  45  	return nil
  46  }
  47  
  48  type queryResult struct {
  49  	Verifier  verifier
  50  	KeyAlgo   string
  51  	HashAlgos []string
  52  	Notes     string
  53  	Services  []string
  54  	Flags     []string
  55  }
  56  
  57  // QueryMethod is a DKIM query method.
  58  type QueryMethod string
  59  
  60  const (
  61  	// DNS TXT resource record (RR) lookup algorithm
  62  	QueryMethodDNSTXT QueryMethod = "dns/txt"
  63  )
  64  
  65  type txtLookupFunc func(domain string) ([]string, error)
  66  type queryFunc func(domain, selector string, txtLookup txtLookupFunc) (*queryResult, error)
  67  
  68  var queryMethods = map[QueryMethod]queryFunc{
  69  	QueryMethodDNSTXT: queryDNSTXT,
  70  }
  71  
  72  func queryDNSTXT(domain, selector string, txtLookup txtLookupFunc) (*queryResult, error) {
  73  	if txtLookup == nil {
  74  		txtLookup = net.LookupTXT
  75  	}
  76  
  77  	txts, err := txtLookup(selector + "._domainkey." + domain)
  78  	if netErr, ok := err.(net.Error); ok && netErr.Temporary() {
  79  		return nil, tempFailError("key unavailable: " + err.Error())
  80  	} else if err != nil {
  81  		return nil, permFailError("no key for signature: " + err.Error())
  82  	}
  83  
  84  	// net.LookupTXT will concatenate strings contained in a single TXT record.
  85  	// In other words, net.LookupTXT returns one entry per TXT record, even if
  86  	// a record contains multiple strings.
  87  	//
  88  	// RFC 6376 section 3.6.2.2 says multiple TXT records lead to undefined
  89  	// behavior, so reject that.
  90  	switch len(txts) {
  91  	case 0:
  92  		return nil, permFailError("no valid key found")
  93  	case 1:
  94  		return parsePublicKey(txts[0])
  95  	default:
  96  		return nil, permFailError("multiple TXT records found for key")
  97  	}
  98  }
  99  
 100  func parsePublicKey(s string) (*queryResult, error) {
 101  	params, err := parseHeaderParams(s)
 102  	if err != nil {
 103  		return nil, permFailError("key record error: " + err.Error())
 104  	}
 105  
 106  	res := new(queryResult)
 107  
 108  	if v, ok := params["v"]; ok && v != "DKIM1" {
 109  		return nil, permFailError("incompatible public key version")
 110  	}
 111  
 112  	p, ok := params["p"]
 113  	if !ok {
 114  		return nil, permFailError("key syntax error: missing public key data")
 115  	}
 116  	if p == "" {
 117  		return nil, permFailError("key revoked")
 118  	}
 119  	p = strings.ReplaceAll(p, " ", "")
 120  	b, err := base64.StdEncoding.DecodeString(p)
 121  	if err != nil {
 122  		return nil, permFailError("key syntax error: " + err.Error())
 123  	}
 124  	switch params["k"] {
 125  	case "rsa", "":
 126  		pub, err := x509.ParsePKIXPublicKey(b)
 127  		if err != nil {
 128  			// RFC 6376 is inconsistent about whether RSA public keys should
 129  			// be formatted as RSAPublicKey or SubjectPublicKeyInfo.
 130  			// Erratum 3017 (https://www.rfc-editor.org/errata/eid3017) proposes
 131  			// allowing both.
 132  			pub, err = x509.ParsePKCS1PublicKey(b)
 133  			if err != nil {
 134  				return nil, permFailError("key syntax error: " + err.Error())
 135  			}
 136  		}
 137  		rsaPub, ok := pub.(*rsa.PublicKey)
 138  		if !ok {
 139  			return nil, permFailError("key syntax error: not an RSA public key")
 140  		}
 141  		// RFC 8301 section 3.2: verifiers MUST NOT consider signatures using
 142  		// RSA keys of less than 1024 bits as valid signatures.
 143  		if rsaPub.Size()*8 < 1024 {
 144  			return nil, permFailError(fmt.Sprintf("key is too short: want 1024 bits, has %v bits", rsaPub.Size()*8))
 145  		}
 146  		res.Verifier = rsaVerifier{rsaPub}
 147  		res.KeyAlgo = "rsa"
 148  	case "ed25519":
 149  		if len(b) != ed25519.PublicKeySize {
 150  			return nil, permFailError(fmt.Sprintf("invalid Ed25519 public key size: %v bytes", len(b)))
 151  		}
 152  		ed25519Pub := ed25519.PublicKey(b)
 153  		res.Verifier = ed25519Verifier{ed25519Pub}
 154  		res.KeyAlgo = "ed25519"
 155  	default:
 156  		return nil, permFailError("unsupported key algorithm")
 157  	}
 158  
 159  	if hashesStr, ok := params["h"]; ok {
 160  		res.HashAlgos = parseTagList(hashesStr)
 161  	}
 162  	if notes, ok := params["n"]; ok {
 163  		res.Notes = notes
 164  	}
 165  	if servicesStr, ok := params["s"]; ok {
 166  		services := parseTagList(servicesStr)
 167  
 168  		hasWildcard := false
 169  		for _, s := range services {
 170  			if s == "*" {
 171  				hasWildcard = true
 172  				break
 173  			}
 174  		}
 175  		if !hasWildcard {
 176  			res.Services = services
 177  		}
 178  	}
 179  	if flagsStr, ok := params["t"]; ok {
 180  		res.Flags = parseTagList(flagsStr)
 181  	}
 182  
 183  	return res, nil
 184  }
 185