func getBodyHash(body *[]byte, algo string, bodyLength uint) (string, error) {
	var h hash.Hash
	if algo == "sha1" {
		h = sha1.New()
	} else {
		h = sha256.New()
	}
	toH := *body
	if bodyLength != 0 {
		if uint(len(toH)) < bodyLength {
			return "", ErrBadDKimTagLBodyTooShort
		}
		toH = toH[0:bodyLength]
	}
	h.Write(toH)
	return base64.StdEncoding.EncodeToString(h.Sum(nil)), nil
}
getBodyHash return the hash (bas64encoded) of the body
package dkim
import (
	"bytes"
	"container/list"
	"crypto"
	"crypto/rand"
	"crypto/rsa"
	"crypto/sha1"
	"crypto/sha256"
	"crypto/x509"
	"encoding/base64"
	"encoding/pem"
	"hash"
	"regexp"
	"strings"
	"time"
)
const (
	CRLF                = "\r\n"
	TAB                 = " "
	FWS                 = CRLF + TAB
	MaxHeaderLineLength = 70
)
type verifyOutput int
const (
	SUCCESS verifyOutput = 1 + iota
	PERMFAIL
	TEMPFAIL
	NOTSIGNED
	TESTINGSUCCESS
	TESTINGPERMFAIL
	TESTINGTEMPFAIL
)
type SigOptions struct {
	Version uint
	PrivateKey []byte
	Domain string
	Selector string
	Auid string
	Canonicalization string
	Algo string
	Headers []string
	BodyLength uint
	QueryMethods []string
	AddSignatureTimestamp bool
	SignatureExpireIn uint64
	CopiedHeaderFields []string
}
func NewSigOptions() SigOptions {
	return SigOptions{
		Version:               1,
		Canonicalization:      "simple/simple",
		Algo:                  "rsa-sha256",
		Headers:               []string{"from"},
		BodyLength:            0,
		QueryMethods:          []string{"dns/txt"},
		AddSignatureTimestamp: true,
		SignatureExpireIn:     0,
	}
}
func Sign(email *[]byte, options SigOptions) error {
	var privateKey *rsa.PrivateKey
	var err error
	if len(options.PrivateKey) == 0 {
		return ErrSignPrivateKeyRequired
	}
	d, _ := pem.Decode(options.PrivateKey)
	if d == nil {
		return ErrCandNotParsePrivateKey
	}
	if key, err := x509.ParsePKCS1PrivateKey(d.Bytes); err != nil {
		if key, err := x509.ParsePKCS8PrivateKey(d.Bytes); err != nil {
			return ErrCandNotParsePrivateKey
		} else {
			privateKey = key.(*rsa.PrivateKey)
		}
	} else {
		privateKey = key
	}
	if options.Domain == "" {
		return ErrSignDomainRequired
	}
	if options.Selector == "" {
		return ErrSignSelectorRequired
	}
	options.Canonicalization, err = validateCanonicalization(strings.ToLower(options.Canonicalization))
	if err != nil {
		return err
	}
	options.Algo = strings.ToLower(options.Algo)
	if options.Algo != "rsa-sha1" && options.Algo != "rsa-sha256" {
		return ErrSignBadAlgo
	}
	hasFrom := false
	for i, h := range options.Headers {
		h = strings.ToLower(h)
		options.Headers[i] = h
		if h == "from" {
			hasFrom = true
		}
	}
	if !hasFrom {
		return ErrSignHeaderShouldContainsFrom
	}
	headers, body, err := canonicalize(email, options.Canonicalization, options.Headers)
	if err != nil {
		return err
	}
	signHash := strings.Split(options.Algo, "-")
	bodyHash, err := getBodyHash(&body, signHash[1], options.BodyLength)
	if err != nil {
		return err
	}
	dkimHeader := newDkimHeaderBySigOptions(options)
	dHeader := dkimHeader.getHeaderBaseForSigning(bodyHash)
	canonicalizations := strings.Split(options.Canonicalization, "/")
	dHeaderCanonicalized, err := canonicalizeHeader(dHeader, canonicalizations[0])
	if err != nil {
		return err
	}
	headers = append(headers, []byte(dHeaderCanonicalized)...)
	headers = bytes.TrimRight(headers, " \r\n")
	sig, err := getSignature(&headers, privateKey, signHash[1])
	subh := ""
	l := len(subh)
	for _, c := range sig {
		subh += string(c)
		l++
		if l >= MaxHeaderLineLength {
			dHeader += subh + FWS
			subh = ""
			l = 0
		}
	}
	dHeader += subh + CRLF
	*email = append([]byte(dHeader), *email...)
	return nil
}
func Verify(email *[]byte, opts ...DNSOpt) (verifyOutput, error) {
	dkimHeader, err := GetHeader(email)
	if err != nil {
		if err == ErrDkimHeaderNotFound {
			return NOTSIGNED, ErrDkimHeaderNotFound
		}
		return PERMFAIL, err
	}
	pubKey, verifyOutputOnError, err := NewPubKeyRespFromDNS(dkimHeader.Selector, dkimHeader.Domain, opts...)
	if err != nil {
		return verifyOutputOnError, err
	}
	headers, body, err := canonicalize(email, dkimHeader.MessageCanonicalization, dkimHeader.Headers)
	if err != nil {
		return getVerifyOutput(PERMFAIL, err, pubKey.FlagTesting)
	}
	sigHash := strings.Split(dkimHeader.Algorithm, "-")
	compatible := false
	for _, algo := range pubKey.HashAlgo {
		if sigHash[1] == algo {
			compatible = true
			break
		}
	}
	if !compatible {
		return getVerifyOutput(PERMFAIL, ErrVerifyInappropriateHashAlgo, pubKey.FlagTesting)
	}
	if !dkimHeader.SignatureExpiration.IsZero() && dkimHeader.SignatureExpiration.Second() < time.Now().Second() {
		return getVerifyOutput(PERMFAIL, ErrVerifySignatureHasExpired, pubKey.FlagTesting)
	}
	bodyHash, err := getBodyHash(&body, sigHash[1], dkimHeader.BodyLength)
	if err != nil {
		return getVerifyOutput(PERMFAIL, err, pubKey.FlagTesting)
	}
	if bodyHash != dkimHeader.BodyHash {
		return getVerifyOutput(PERMFAIL, ErrVerifyBodyHash, pubKey.FlagTesting)
	}
	dkimHeaderCano, err := canonicalizeHeader(dkimHeader.rawForSign, strings.Split(dkimHeader.MessageCanonicalization, "/")[0])
	if err != nil {
		return getVerifyOutput(TEMPFAIL, err, pubKey.FlagTesting)
	}
	toSignStr := string(headers) + dkimHeaderCano
	toSign := bytes.TrimRight([]byte(toSignStr), " \r\n")
	err = verifySignature(toSign, dkimHeader.SignatureData, &pubKey.PubKey, sigHash[1])
	if err != nil {
		return getVerifyOutput(PERMFAIL, err, pubKey.FlagTesting)
	}
	return SUCCESS, nil
}
func getVerifyOutput(status verifyOutput, err error, flagTesting bool) (verifyOutput, error) {
	if !flagTesting {
		return status, err
	}
	switch status {
	case SUCCESS:
		return TESTINGSUCCESS, err
	case PERMFAIL:
		return TESTINGPERMFAIL, err
	case TEMPFAIL:
		return TESTINGTEMPFAIL, err
	}
	return status, err
}
func canonicalize(email *[]byte, cano string, h []string) (headers, body []byte, err error) {
	body = []byte{}
	rxReduceWS := regexp.MustCompile(`[ \t]+`)
	rawHeaders, rawBody, err := getHeadersBody(email)
	if err != nil {
		return nil, nil, err
	}
	canonicalizations := strings.Split(cano, "/")
	headersList, err := getHeadersList(&rawHeaders)
	var match *list.Element
	headersToKeepList := list.New()
	for _, headerToKeep := range h {
		match = nil
		headerToKeepToLower := strings.ToLower(headerToKeep)
		for e := headersList.Front(); e != nil; e = e.Next() {
			t := strings.Split(e.Value.(string), ":")
			if strings.ToLower(t[0]) == headerToKeepToLower {
				match = e
			}
		}
		if match != nil {
			headersToKeepList.PushBack(match.Value.(string) + "\r\n")
			headersList.Remove(match)
		}
	}
	for e := headersToKeepList.Front(); e != nil; e = e.Next() {
		cHeader, err := canonicalizeHeader(e.Value.(string), canonicalizations[0])
		if err != nil {
			return headers, body, err
		}
		headers = append(headers, []byte(cHeader)...)
	}
	if canonicalizations[1] == "simple" {
		body = bytes.TrimRight(rawBody, "\r\n")
		body = append(body, []byte{13, 10}...)
	} else {
		rawBody = rxReduceWS.ReplaceAll(rawBody, []byte(" "))
		for _, line := range bytes.SplitAfter(rawBody, []byte{10}) {
			line = bytes.TrimRight(line, " \r\n")
			body = append(body, line...)
			body = append(body, []byte{13, 10}...)
		}
		body = bytes.TrimRight(body, "\r\n")
		body = append(body, []byte{13, 10}...)
	}
	return
}
func canonicalizeHeader(header string, algo string) (string, error) {
	if algo == "simple" {
		return header, nil
	} else if algo == "relaxed" {
		kv := strings.SplitN(header, ":", 2)
		if len(kv) != 2 {
			return header, ErrBadMailFormatHeaders
		}
		k := strings.ToLower(kv[0])
		k = strings.TrimSpace(k)
		v := removeFWS(kv[1])
		return k + ":" + v + CRLF, nil
	}
	return header, ErrSignBadCanonicalization
}