API cleanup
This commit is contained in:
111
dkim.go
111
dkim.go
@@ -27,16 +27,6 @@ const (
|
||||
|
||||
type verifyOutput int
|
||||
|
||||
const (
|
||||
SUCCESS verifyOutput = 1 + iota
|
||||
PERMFAIL
|
||||
TEMPFAIL
|
||||
NOTSIGNED
|
||||
TESTINGSUCCESS
|
||||
TESTINGPERMFAIL
|
||||
TESTINGTEMPFAIL
|
||||
)
|
||||
|
||||
// sigOptions represents signing options
|
||||
type SigOptions struct {
|
||||
|
||||
@@ -98,40 +88,40 @@ func NewSigOptions() SigOptions {
|
||||
}
|
||||
|
||||
// Sign signs an email
|
||||
func Sign(email *[]byte, options SigOptions) error {
|
||||
func Sign(email []byte, options SigOptions) ([]byte, error) {
|
||||
var privateKey *rsa.PrivateKey
|
||||
|
||||
// PrivateKey
|
||||
if len(options.PrivateKey) == 0 {
|
||||
return ErrSignPrivateKeyRequired
|
||||
return nil, ErrSignPrivateKeyRequired
|
||||
}
|
||||
d, _ := pem.Decode(options.PrivateKey)
|
||||
key, err := x509.ParsePKCS1PrivateKey(d.Bytes)
|
||||
if err != nil {
|
||||
return ErrCandNotParsePrivateKey
|
||||
return nil, ErrCandNotParsePrivateKey
|
||||
}
|
||||
privateKey = key
|
||||
|
||||
// Domain required
|
||||
if options.Domain == "" {
|
||||
return ErrSignDomainRequired
|
||||
return nil, ErrSignDomainRequired
|
||||
}
|
||||
|
||||
// Selector required
|
||||
if options.Selector == "" {
|
||||
return ErrSignSelectorRequired
|
||||
return nil, ErrSignSelectorRequired
|
||||
}
|
||||
|
||||
// Canonicalization
|
||||
options.Canonicalization, err = validateCanonicalization(strings.ToLower(options.Canonicalization))
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Algo
|
||||
options.Algo = strings.ToLower(options.Algo)
|
||||
if options.Algo != "rsa-sha1" && options.Algo != "rsa-sha256" {
|
||||
return ErrSignBadAlgo
|
||||
return nil, ErrSignBadAlgo
|
||||
}
|
||||
|
||||
// Header must contain "from"
|
||||
@@ -144,21 +134,20 @@ func Sign(email *[]byte, options SigOptions) error {
|
||||
}
|
||||
}
|
||||
if !hasFrom {
|
||||
return ErrSignHeaderShouldContainsFrom
|
||||
return nil, ErrSignHeaderShouldContainsFrom
|
||||
}
|
||||
|
||||
// Normalize
|
||||
headers, body, err := canonicalize(email, options.Canonicalization, options.Headers)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
signHash := strings.Split(options.Algo, "-")
|
||||
|
||||
// hash body
|
||||
bodyHash, err := getBodyHash(&body, signHash[1], options.BodyLength)
|
||||
bodyHash, err := getBodyHash(body, signHash[1], options.BodyLength)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Get dkim header base
|
||||
@@ -168,7 +157,7 @@ func Sign(email *[]byte, options SigOptions) error {
|
||||
canonicalizations := strings.Split(options.Canonicalization, "/")
|
||||
dHeaderCanonicalized, err := canonicalizeHeader(dHeader, canonicalizations[0])
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
headers = append(headers, []byte(dHeaderCanonicalized)...)
|
||||
headers = bytes.TrimRight(headers, " \r\n")
|
||||
@@ -189,35 +178,26 @@ func Sign(email *[]byte, options SigOptions) error {
|
||||
}
|
||||
}
|
||||
dHeader += subh + CRLF
|
||||
*email = append([]byte(dHeader), *email...)
|
||||
return nil
|
||||
return append([]byte(dHeader), email...), nil
|
||||
}
|
||||
|
||||
// verify verifies an email an return
|
||||
// state: SUCCESS or PERMFAIL or TEMPFAIL, TESTINGSUCCESS, TESTINGPERMFAIL
|
||||
// TESTINGTEMPFAIL or NOTSIGNED
|
||||
// error: if an error occurs during verification
|
||||
func Verify(email *[]byte) (verifyOutput, error) {
|
||||
func Verify(email []byte) (dkimHeader *DKIMHeader, err error) {
|
||||
// parse email
|
||||
dkimHeader, err := newDkimHeaderFromEmail(email)
|
||||
dkimHeader, err = newDkimHeaderFromEmail(email)
|
||||
if err != nil {
|
||||
if err == ErrDkimHeaderNotFound {
|
||||
return NOTSIGNED, ErrDkimHeaderNotFound
|
||||
} else {
|
||||
return PERMFAIL, err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// we do not set query method because if it's others, validation failed earlier
|
||||
pubKey, verifyOutputOnError, err := newPubKeyFromDnsTxt(dkimHeader.Selector, dkimHeader.Domain)
|
||||
pubKey, err := newPubKeyFromDnsTxt(dkimHeader.Selector, dkimHeader.Domain)
|
||||
if err != nil {
|
||||
return getVerifyOutput(verifyOutputOnError, err, pubKey.FlagTesting)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Normalize
|
||||
headers, body, err := canonicalize(email, dkimHeader.MessageCanonicalization, dkimHeader.Headers)
|
||||
if err != nil {
|
||||
return getVerifyOutput(PERMFAIL, err, pubKey.FlagTesting)
|
||||
return nil, err
|
||||
}
|
||||
sigHash := strings.Split(dkimHeader.Algorithm, "-")
|
||||
// check if hash algo are compatible
|
||||
@@ -229,60 +209,43 @@ func Verify(email *[]byte) (verifyOutput, error) {
|
||||
}
|
||||
}
|
||||
if !compatible {
|
||||
return getVerifyOutput(PERMFAIL, ErrVerifyInappropriateHashAlgo, pubKey.FlagTesting)
|
||||
return nil, ErrVerifyInappropriateHashAlgo
|
||||
}
|
||||
|
||||
// expired ?
|
||||
if !dkimHeader.SignatureExpiration.IsZero() && dkimHeader.SignatureExpiration.Second() < time.Now().Second() {
|
||||
return getVerifyOutput(PERMFAIL, ErrVerifySignatureHasExpired, pubKey.FlagTesting)
|
||||
|
||||
return nil, ErrVerifySignatureHasExpired
|
||||
}
|
||||
|
||||
//println("|" + string(body) + "|")
|
||||
// get body hash
|
||||
bodyHash, err := getBodyHash(&body, sigHash[1], dkimHeader.BodyLength)
|
||||
bodyHash, err := getBodyHash(body, sigHash[1], dkimHeader.BodyLength)
|
||||
if err != nil {
|
||||
return getVerifyOutput(PERMFAIL, err, pubKey.FlagTesting)
|
||||
return nil, err
|
||||
}
|
||||
//println(bodyHash)
|
||||
|
||||
if bodyHash != dkimHeader.BodyHash {
|
||||
return getVerifyOutput(PERMFAIL, ErrVerifyBodyHash, pubKey.FlagTesting)
|
||||
return nil, ErrVerifyBodyHash
|
||||
}
|
||||
|
||||
// compute sig
|
||||
dkimHeaderCano, err := canonicalizeHeader(dkimHeader.RawForSign, strings.Split(dkimHeader.MessageCanonicalization, "/")[0])
|
||||
if err != nil {
|
||||
return getVerifyOutput(TEMPFAIL, err, pubKey.FlagTesting)
|
||||
return nil, err
|
||||
}
|
||||
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 nil, err
|
||||
}
|
||||
return SUCCESS, nil
|
||||
}
|
||||
|
||||
// getVerifyOutput returns output of verify fct according to the testing flag
|
||||
func getVerifyOutput(status verifyOutput, err error, flagTesting bool) (verifyOutput, error) {
|
||||
if !flagTesting {
|
||||
return status, err
|
||||
if pubKey.FlagTesting {
|
||||
err = ErrTesting
|
||||
}
|
||||
switch status {
|
||||
case SUCCESS:
|
||||
return TESTINGSUCCESS, err
|
||||
case PERMFAIL:
|
||||
return TESTINGPERMFAIL, err
|
||||
case TEMPFAIL:
|
||||
return TESTINGTEMPFAIL, err
|
||||
}
|
||||
// should never happen but compilator sream whithout return
|
||||
return status, err
|
||||
return
|
||||
}
|
||||
|
||||
// canonicalize returns canonicalized version of header and body
|
||||
func canonicalize(email *[]byte, cano string, h []string) (headers, body []byte, err error) {
|
||||
func canonicalize(email []byte, cano string, h []string) (headers, body []byte, err error) {
|
||||
body = []byte{}
|
||||
rxReduceWS := regexp.MustCompile(`[ \t]+`)
|
||||
|
||||
@@ -302,7 +265,6 @@ func canonicalize(email *[]byte, cano string, h []string) (headers, body []byte,
|
||||
match = nil
|
||||
headerToKeepToLower := strings.ToLower(headerToKeep)
|
||||
for e := headersList.Front(); e != nil; e = e.Next() {
|
||||
//fmt.Printf("|%s|\n", e.Value.(string))
|
||||
t := strings.Split(e.Value.(string), ":")
|
||||
if strings.ToLower(t[0]) == headerToKeepToLower {
|
||||
match = e
|
||||
@@ -408,14 +370,14 @@ func canonicalizeHeader(header string, algo string) (string, error) {
|
||||
}
|
||||
|
||||
// getBodyHash return the hash (bas64encoded) of the body
|
||||
func getBodyHash(body *[]byte, algo string, bodyLength uint) (string, error) {
|
||||
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
|
||||
toH := body
|
||||
// if l tag (body length)
|
||||
if bodyLength != 0 {
|
||||
if uint(len(toH)) < bodyLength {
|
||||
@@ -479,9 +441,10 @@ func verifySignature(toSign []byte, sig64 string, key *rsa.PublicKey, algo strin
|
||||
return rsa.VerifyPKCS1v15(key, h2, h1.Sum(nil), sig)
|
||||
}
|
||||
|
||||
var rxReduceWS = regexp.MustCompile(`[ \t]+`)
|
||||
|
||||
// removeFWS removes all FWS from string
|
||||
func removeFWS(in string) string {
|
||||
rxReduceWS := regexp.MustCompile(`[ \t]+`)
|
||||
out := strings.Replace(in, "\n", "", -1)
|
||||
out = strings.Replace(out, "\r", "", -1)
|
||||
out = rxReduceWS.ReplaceAllString(out, " ")
|
||||
@@ -529,9 +492,9 @@ func getHeadersList(rawHeader *[]byte) (*list.List, error) {
|
||||
}
|
||||
|
||||
// getHeadersBody return headers and body
|
||||
func getHeadersBody(email *[]byte) ([]byte, []byte, error) {
|
||||
func getHeadersBody(email []byte) ([]byte, []byte, error) {
|
||||
// TODO: \n -> \r\n
|
||||
parts := bytes.SplitN(*email, []byte{13, 10, 13, 10}, 2)
|
||||
parts := bytes.SplitN(email, []byte{13, 10, 13, 10}, 2)
|
||||
if len(parts) != 2 {
|
||||
return []byte{}, []byte{}, ErrBadMailFormat
|
||||
}
|
||||
|
||||
@@ -10,7 +10,8 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type dkimHeader struct {
|
||||
// DKIMHeader
|
||||
type DKIMHeader struct {
|
||||
// Version This tag defines the version of DKIM
|
||||
// specification that applies to the signature record.
|
||||
// tag v
|
||||
@@ -99,7 +100,7 @@ type dkimHeader struct {
|
||||
// Internationalized domain names MUST be encoded as A-labels, as
|
||||
// described in Section 2.3 of [RFC5890].
|
||||
// tag i
|
||||
Auid string
|
||||
AUID string
|
||||
|
||||
// Body length count (plain-text unsigned decimal integer; OPTIONAL,
|
||||
// default is entire body). This tag informs the Verifier of the
|
||||
@@ -197,14 +198,14 @@ type dkimHeader struct {
|
||||
}
|
||||
|
||||
// NewDkimHeaderBySigOptions return a new DkimHeader initioalized with sigOptions value
|
||||
func newDkimHeaderBySigOptions(options SigOptions) *dkimHeader {
|
||||
h := new(dkimHeader)
|
||||
func newDkimHeaderBySigOptions(options SigOptions) *DKIMHeader {
|
||||
h := new(DKIMHeader)
|
||||
h.Version = "1"
|
||||
h.Algorithm = options.Algo
|
||||
h.MessageCanonicalization = options.Canonicalization
|
||||
h.Domain = options.Domain
|
||||
h.Headers = options.Headers
|
||||
h.Auid = options.Auid
|
||||
h.AUID = options.Auid
|
||||
h.BodyLength = options.BodyLength
|
||||
h.QueryMethods = options.QueryMethods
|
||||
h.Selector = options.Selector
|
||||
@@ -221,8 +222,8 @@ func newDkimHeaderBySigOptions(options SigOptions) *dkimHeader {
|
||||
// NewFromEmail return a new DkimHeader by parsing an email
|
||||
// Note: according to RFC 6376 an email can have multiple DKIM Header
|
||||
// in this case we return the last inserted or the last with d== mail from
|
||||
func newDkimHeaderFromEmail(email *[]byte) (*dkimHeader, error) {
|
||||
m, err := mail.ReadMessage(bytes.NewReader(*email))
|
||||
func newDkimHeaderFromEmail(email []byte) (*DKIMHeader, error) {
|
||||
m, err := mail.ReadMessage(bytes.NewReader(email))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -265,7 +266,7 @@ func newDkimHeaderFromEmail(email *[]byte) (*dkimHeader, error) {
|
||||
}
|
||||
}
|
||||
|
||||
var keep *dkimHeader
|
||||
var keep *DKIMHeader
|
||||
var keepErr error
|
||||
//for _, dk := range m.Header[textproto.CanonicalMIMEHeaderKey("DKIM-Signature")] {
|
||||
for _, h := range dkHeaders {
|
||||
@@ -291,8 +292,8 @@ func newDkimHeaderFromEmail(email *[]byte) (*dkimHeader, error) {
|
||||
}
|
||||
|
||||
// parseDkHeader parse raw dkim header
|
||||
func parseDkHeader(header string) (dkh *dkimHeader, err error) {
|
||||
dkh = new(dkimHeader)
|
||||
func parseDkHeader(header string) (dkh *DKIMHeader, err error) {
|
||||
dkh = new(DKIMHeader)
|
||||
|
||||
keyVal := strings.SplitN(header, ":", 2)
|
||||
|
||||
@@ -384,7 +385,7 @@ func parseDkHeader(header string) (dkh *dkimHeader, err error) {
|
||||
if !strings.HasSuffix(data, dkh.Domain) {
|
||||
return nil, ErrDkimHeaderDomainMismatch
|
||||
}
|
||||
dkh.Auid = data
|
||||
dkh.AUID = data
|
||||
}
|
||||
case "l":
|
||||
ui, err := strconv.ParseUint(data, 10, 32)
|
||||
@@ -423,8 +424,8 @@ func parseDkHeader(header string) (dkh *dkimHeader, err error) {
|
||||
}
|
||||
|
||||
// default for i/Auid
|
||||
if dkh.Auid == "" {
|
||||
dkh.Auid = "@" + dkh.Domain
|
||||
if dkh.AUID == "" {
|
||||
dkh.AUID = "@" + dkh.Domain
|
||||
}
|
||||
|
||||
// defaut for query method
|
||||
@@ -438,7 +439,7 @@ func parseDkHeader(header string) (dkh *dkimHeader, err error) {
|
||||
|
||||
// GetHeaderBase return base header for signers
|
||||
// Todo: some refactoring needed...
|
||||
func (d *dkimHeader) getHeaderBaseForSigning(bodyHash string) string {
|
||||
func (d *DKIMHeader) getHeaderBaseForSigning(bodyHash string) string {
|
||||
h := "DKIM-Signature: v=" + d.Version + "; a=" + d.Algorithm + "; q=" + strings.Join(d.QueryMethods, ":") + "; c=" + d.MessageCanonicalization + ";" + CRLF + TAB
|
||||
subh := "s=" + d.Selector + ";"
|
||||
if len(subh)+len(d.Domain)+4 > MaxHeaderLineLength {
|
||||
@@ -448,12 +449,12 @@ func (d *dkimHeader) getHeaderBaseForSigning(bodyHash string) string {
|
||||
subh += " d=" + d.Domain + ";"
|
||||
|
||||
// Auid
|
||||
if len(d.Auid) != 0 {
|
||||
if len(subh)+len(d.Auid)+4 > MaxHeaderLineLength {
|
||||
if len(d.AUID) != 0 {
|
||||
if len(subh)+len(d.AUID)+4 > MaxHeaderLineLength {
|
||||
h += subh + FWS
|
||||
subh = ""
|
||||
}
|
||||
subh += " i=" + d.Auid + ";"
|
||||
subh += " i=" + d.AUID + ";"
|
||||
}
|
||||
|
||||
/*h := "DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=tmail.io; i=@tmail.io;" + FWS
|
||||
|
||||
75
dkim_test.go
75
dkim_test.go
@@ -212,57 +212,57 @@ func Test_SignConfig(t *testing.T) {
|
||||
email := []byte(emailBase)
|
||||
emailToTest := append([]byte(nil), email...)
|
||||
options := NewSigOptions()
|
||||
err := Sign(&emailToTest, options)
|
||||
_, err := Sign(emailToTest, options)
|
||||
assert.NotNil(t, err)
|
||||
// && err No private key
|
||||
assert.EqualError(t, err, ErrSignPrivateKeyRequired.Error())
|
||||
options.PrivateKey = []byte(privKey)
|
||||
emailToTest = append([]byte(nil), email...)
|
||||
err = Sign(&emailToTest, options)
|
||||
_, err = Sign(emailToTest, options)
|
||||
|
||||
// Domain
|
||||
assert.EqualError(t, err, ErrSignDomainRequired.Error())
|
||||
options.Domain = "toorop.fr"
|
||||
emailToTest = append([]byte(nil), email...)
|
||||
err = Sign(&emailToTest, options)
|
||||
_, err = Sign(emailToTest, options)
|
||||
|
||||
// Selector
|
||||
assert.Error(t, err, ErrSignSelectorRequired.Error())
|
||||
options.Selector = "default"
|
||||
emailToTest = append([]byte(nil), email...)
|
||||
err = Sign(&emailToTest, options)
|
||||
_, err = Sign(emailToTest, options)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Canonicalization
|
||||
options.Canonicalization = "simple/relaxed/simple"
|
||||
emailToTest = append([]byte(nil), email...)
|
||||
err = Sign(&emailToTest, options)
|
||||
_, err = Sign(emailToTest, options)
|
||||
assert.EqualError(t, err, ErrSignBadCanonicalization.Error())
|
||||
|
||||
options.Canonicalization = "simple/relax"
|
||||
emailToTest = append([]byte(nil), email...)
|
||||
err = Sign(&emailToTest, options)
|
||||
_, err = Sign(emailToTest, options)
|
||||
assert.EqualError(t, err, ErrSignBadCanonicalization.Error())
|
||||
|
||||
options.Canonicalization = "relaxed"
|
||||
emailToTest = append([]byte(nil), email...)
|
||||
err = Sign(&emailToTest, options)
|
||||
_, err = Sign(emailToTest, options)
|
||||
assert.NoError(t, err)
|
||||
|
||||
options.Canonicalization = "SiMple/relAxed"
|
||||
emailToTest = append([]byte(nil), email...)
|
||||
err = Sign(&emailToTest, options)
|
||||
_, err = Sign(emailToTest, options)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// header
|
||||
options.Headers = []string{"toto"}
|
||||
emailToTest = append([]byte(nil), email...)
|
||||
err = Sign(&emailToTest, options)
|
||||
_, err = Sign(emailToTest, options)
|
||||
assert.EqualError(t, err, ErrSignHeaderShouldContainsFrom.Error())
|
||||
|
||||
options.Headers = []string{"To", "From"}
|
||||
emailToTest = append([]byte(nil), email...)
|
||||
err = Sign(&emailToTest, options)
|
||||
_, err = Sign(emailToTest, options)
|
||||
assert.NoError(t, err)
|
||||
|
||||
}
|
||||
@@ -274,18 +274,18 @@ func Test_canonicalize(t *testing.T) {
|
||||
options.Headers = []string{"from", "date", "mime-version", "received", "received", "In-Reply-To"}
|
||||
// simple/simple
|
||||
options.Canonicalization = "simple/simple"
|
||||
header, body, err := canonicalize(&emailToTest, options.Canonicalization, options.Headers)
|
||||
header, body, err := canonicalize(emailToTest, options.Canonicalization, options.Headers)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []byte(headerSimple), header)
|
||||
assert.Equal(t, []byte(bodySimple), body)
|
||||
assert.Equal(t, headerSimple, string(header))
|
||||
assert.Equal(t, bodySimple, string(body))
|
||||
|
||||
// relaxed/relaxed
|
||||
emailToTest = append([]byte(nil), email...)
|
||||
options.Canonicalization = "relaxed/relaxed"
|
||||
header, body, err = canonicalize(&emailToTest, options.Canonicalization, options.Headers)
|
||||
header, body, err = canonicalize(emailToTest, options.Canonicalization, options.Headers)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []byte(headerRelaxed), header)
|
||||
assert.Equal(t, []byte(bodyRelaxed), body)
|
||||
assert.Equal(t, headerRelaxed, string(header))
|
||||
assert.Equal(t, bodyRelaxed, string(body))
|
||||
|
||||
}
|
||||
|
||||
@@ -301,77 +301,70 @@ func Test_Sign(t *testing.T) {
|
||||
options.AddSignatureTimestamp = false
|
||||
|
||||
options.Canonicalization = "relaxed/relaxed"
|
||||
err := Sign(&emailRelaxed, options)
|
||||
emailRelaxed, err := Sign(emailRelaxed, options)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []byte(signedRelaxedRelaxed), emailRelaxed)
|
||||
assert.Equal(t, signedRelaxedRelaxed, string(emailRelaxed))
|
||||
|
||||
options.BodyLength = 5
|
||||
emailRelaxed = append([]byte(nil), email...)
|
||||
err = Sign(&emailRelaxed, options)
|
||||
emailRelaxed, err = Sign(emailRelaxed, options)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []byte(signedRelaxedRelaxedLength), emailRelaxed)
|
||||
assert.Equal(t, signedRelaxedRelaxedLength, string(emailRelaxed))
|
||||
|
||||
options.BodyLength = 0
|
||||
options.Canonicalization = "simple/simple"
|
||||
emailSimple := append([]byte(nil), email...)
|
||||
err = Sign(&emailSimple, options)
|
||||
assert.Equal(t, []byte(signedSimpleSimple), emailSimple)
|
||||
emailSimple, err = Sign(emailSimple, options)
|
||||
assert.Equal(t, signedSimpleSimple, string(emailSimple))
|
||||
|
||||
options.Headers = []string{"from", "subject", "date", "message-id"}
|
||||
memail := []byte(missingHeaderMail)
|
||||
err = Sign(&memail, options)
|
||||
_, err = Sign(memail, options)
|
||||
assert.NoError(t, err)
|
||||
|
||||
options.BodyLength = 5
|
||||
options.Canonicalization = "simple/simple"
|
||||
emailSimple = append([]byte(nil), email...)
|
||||
err = Sign(&emailSimple, options)
|
||||
assert.Equal(t, []byte(signedSimpleSimpleLength), emailSimple)
|
||||
emailSimple, err = Sign(emailSimple, options)
|
||||
assert.Equal(t, signedSimpleSimpleLength, string(emailSimple))
|
||||
|
||||
}
|
||||
|
||||
func Test_Verify(t *testing.T) {
|
||||
// no DKIM header
|
||||
email := []byte(emailBase)
|
||||
status, err := Verify(&email)
|
||||
assert.Equal(t, NOTSIGNED, status)
|
||||
_, err := Verify(email)
|
||||
assert.Equal(t, ErrDkimHeaderNotFound, err)
|
||||
|
||||
// No From
|
||||
email = []byte(signedNoFrom)
|
||||
status, err = Verify(&email)
|
||||
_, err = Verify(email)
|
||||
assert.Equal(t, ErrVerifyBodyHash, err)
|
||||
assert.Equal(t, TESTINGPERMFAIL, status) // cause we use dkheader of the "with from" email
|
||||
|
||||
// missing mandatory 'a' flag
|
||||
email = []byte(signedMissingFlag)
|
||||
status, err = Verify(&email)
|
||||
_, err = Verify(email)
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, PERMFAIL, status)
|
||||
assert.Equal(t, ErrDkimHeaderMissingRequiredTag, err)
|
||||
|
||||
// missing bad algo
|
||||
email = []byte(signedBadAlgo)
|
||||
status, err = Verify(&email)
|
||||
assert.Equal(t, PERMFAIL, status)
|
||||
_, err = Verify(email)
|
||||
assert.Equal(t, ErrSignBadAlgo, err)
|
||||
|
||||
// relaxed
|
||||
email = []byte(signedRelaxedRelaxedLength)
|
||||
status, err = Verify(&email)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, SUCCESS, status)
|
||||
_, err = Verify(email)
|
||||
assert.Equal(t, ErrTesting, err)
|
||||
|
||||
// simple
|
||||
email = []byte(signedSimpleSimpleLength)
|
||||
status, err = Verify(&email)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, SUCCESS, status)
|
||||
_, err = Verify(email)
|
||||
assert.Equal(t, ErrTesting, err)
|
||||
|
||||
// gmail
|
||||
email = []byte(fromGmail)
|
||||
status, err = Verify(&email)
|
||||
_, err = Verify(email)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, SUCCESS, status)
|
||||
|
||||
}
|
||||
|
||||
@@ -62,7 +62,8 @@ var (
|
||||
// ErrVerifyNoKeyForSignature
|
||||
ErrVerifyNoKeyForSignature = errors.New("no key for verify")
|
||||
|
||||
// ErrVerifyKeyUnavailable when service (dns) is anavailable
|
||||
// ErrVerifyKeyUnavailable when service (dns) is anavailable.
|
||||
// This error may be temporary in some cases.
|
||||
ErrVerifyKeyUnavailable = errors.New("key unavailable")
|
||||
|
||||
// ErrVerifyTagVMustBeTheFirst if present the v tag must be the firts in the record
|
||||
@@ -88,4 +89,7 @@ var (
|
||||
|
||||
// ErrVerifyInappropriateHashAlgo when h tag in pub key doesn't contain hash algo from a tag of DKIM header
|
||||
ErrVerifyInappropriateHashAlgo = errors.New("inappropriate has algorithm")
|
||||
|
||||
// ErrTesting
|
||||
ErrTesting = errors.New("public key has testing flag set")
|
||||
)
|
||||
|
||||
22
pubKeyRep.go
22
pubKeyRep.go
@@ -20,19 +20,19 @@ type pubKeyRep struct {
|
||||
FlagIMustBeD bool // flag i
|
||||
}
|
||||
|
||||
func newPubKeyFromDnsTxt(selector, domain string) (*pubKeyRep, verifyOutput, error) {
|
||||
func newPubKeyFromDnsTxt(selector, domain string) (*pubKeyRep, error) {
|
||||
txt, err := net.LookupTXT(selector + "._domainkey." + domain)
|
||||
if err != nil {
|
||||
if strings.HasSuffix(err.Error(), "no such host") {
|
||||
return nil, PERMFAIL, ErrVerifyNoKeyForSignature
|
||||
return nil, ErrVerifyNoKeyForSignature
|
||||
} else {
|
||||
return nil, TEMPFAIL, ErrVerifyKeyUnavailable
|
||||
return nil, ErrVerifyKeyUnavailable
|
||||
}
|
||||
}
|
||||
|
||||
// empty record
|
||||
if len(txt) == 0 {
|
||||
return nil, PERMFAIL, ErrVerifyNoKeyForSignature
|
||||
return nil, ErrVerifyNoKeyForSignature
|
||||
}
|
||||
|
||||
pkr := new(pubKeyRep)
|
||||
@@ -57,11 +57,11 @@ func newPubKeyFromDnsTxt(selector, domain string) (*pubKeyRep, verifyOutput, err
|
||||
case "v":
|
||||
// RFC: is this tag is specified it MUST be the first in the record
|
||||
if i != 0 {
|
||||
return nil, PERMFAIL, ErrVerifyTagVMustBeTheFirst
|
||||
return nil, ErrVerifyTagVMustBeTheFirst
|
||||
}
|
||||
pkr.Version = val
|
||||
if pkr.Version != "DKIM1" {
|
||||
return nil, PERMFAIL, ErrVerifyVersionMusBeDkim1
|
||||
return nil, ErrVerifyVersionMusBeDkim1
|
||||
}
|
||||
case "h":
|
||||
p := strings.Split(strings.ToLower(val), ":")
|
||||
@@ -78,18 +78,18 @@ func newPubKeyFromDnsTxt(selector, domain string) (*pubKeyRep, verifyOutput, err
|
||||
}
|
||||
case "k":
|
||||
if strings.ToLower(val) != "rsa" {
|
||||
return nil, PERMFAIL, ErrVerifyBadKeyType
|
||||
return nil, ErrVerifyBadKeyType
|
||||
}
|
||||
case "n":
|
||||
pkr.Note = val
|
||||
case "p":
|
||||
rawkey := val
|
||||
if rawkey == "" {
|
||||
return nil, PERMFAIL, ErrVerifyRevokedKey
|
||||
return nil, ErrVerifyRevokedKey
|
||||
}
|
||||
un64, err := base64.StdEncoding.DecodeString(rawkey)
|
||||
if err != nil {
|
||||
return nil, PERMFAIL, ErrVerifyBadKey
|
||||
return nil, ErrVerifyBadKey
|
||||
}
|
||||
pk, err := x509.ParsePKIXPublicKey(un64)
|
||||
pkr.PubKey = *pk.(*rsa.PublicKey)
|
||||
@@ -120,8 +120,8 @@ func newPubKeyFromDnsTxt(selector, domain string) (*pubKeyRep, verifyOutput, err
|
||||
|
||||
// if no pubkey
|
||||
if pkr.PubKey == (rsa.PublicKey{}) {
|
||||
return nil, PERMFAIL, ErrVerifyNoKey
|
||||
return nil, ErrVerifyNoKey
|
||||
}
|
||||
|
||||
return pkr, SUCCESS, nil
|
||||
return pkr, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user