allow mocking
This commit is contained in:
30
dkim.go
30
dkim.go
@@ -13,6 +13,7 @@ import (
|
|||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"hash"
|
"hash"
|
||||||
|
"net"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -25,8 +26,6 @@ const (
|
|||||||
MaxHeaderLineLength = 70
|
MaxHeaderLineLength = 70
|
||||||
)
|
)
|
||||||
|
|
||||||
type verifyOutput int
|
|
||||||
|
|
||||||
// sigOptions represents signing options
|
// sigOptions represents signing options
|
||||||
type SigOptions struct {
|
type SigOptions struct {
|
||||||
|
|
||||||
@@ -181,26 +180,36 @@ func Sign(email []byte, options SigOptions) ([]byte, error) {
|
|||||||
return append([]byte(dHeader), email...), nil
|
return append([]byte(dHeader), email...), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func Verify(email []byte) (dkimHeader *DKIMHeader, err error) {
|
func Verify(email []byte, LookupTXT func(string) ([]string, error), now func() time.Time) (dkimHeader *DKIMHeader, err error) {
|
||||||
// parse email
|
if LookupTXT == nil {
|
||||||
|
LookupTXT = net.LookupTXT
|
||||||
|
}
|
||||||
|
if now == nil {
|
||||||
|
now = time.Now
|
||||||
|
}
|
||||||
|
|
||||||
dkimHeader, err = newDkimHeaderFromEmail(email)
|
dkimHeader, err = newDkimHeaderFromEmail(email)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// we do not set query method because if it's others, validation failed earlier
|
txt, err := LookupTXT(dkimHeader.Selector + "._domainkey." + dkimHeader.Domain)
|
||||||
pubKey, err := newPubKeyFromDnsTxt(dkimHeader.Selector, dkimHeader.Domain)
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
pubKey, err := newPubKeyFromDnsTxt(txt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normalize
|
|
||||||
headers, body, err := canonicalize(email, dkimHeader.MessageCanonicalization, dkimHeader.Headers)
|
headers, body, err := canonicalize(email, dkimHeader.MessageCanonicalization, dkimHeader.Headers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
sigHash := strings.Split(dkimHeader.Algorithm, "-")
|
sigHash := strings.Split(dkimHeader.Algorithm, "-")
|
||||||
// check if hash algo are compatible
|
if len(sigHash) < 2 {
|
||||||
|
return nil, ErrVerifyInappropriateHashAlgo
|
||||||
|
}
|
||||||
compatible := false
|
compatible := false
|
||||||
for _, algo := range pubKey.HashAlgo {
|
for _, algo := range pubKey.HashAlgo {
|
||||||
if sigHash[1] == algo {
|
if sigHash[1] == algo {
|
||||||
@@ -212,10 +221,11 @@ func Verify(email []byte) (dkimHeader *DKIMHeader, err error) {
|
|||||||
return nil, ErrVerifyInappropriateHashAlgo
|
return nil, ErrVerifyInappropriateHashAlgo
|
||||||
}
|
}
|
||||||
|
|
||||||
// expired ?
|
if !dkimHeader.SignatureExpiration.IsZero() {
|
||||||
if !dkimHeader.SignatureExpiration.IsZero() && dkimHeader.SignatureExpiration.Second() < time.Now().Second() {
|
if dkimHeader.SignatureExpiration.Before(now()) {
|
||||||
return nil, ErrVerifySignatureHasExpired
|
return nil, ErrVerifySignatureHasExpired
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bodyHash, err := getBodyHash(body, sigHash[1], dkimHeader.BodyLength)
|
bodyHash, err := getBodyHash(body, sigHash[1], dkimHeader.BodyLength)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
14
dkim_test.go
14
dkim_test.go
@@ -333,38 +333,38 @@ func Test_Sign(t *testing.T) {
|
|||||||
func Test_Verify(t *testing.T) {
|
func Test_Verify(t *testing.T) {
|
||||||
// no DKIM header
|
// no DKIM header
|
||||||
email := []byte(emailBase)
|
email := []byte(emailBase)
|
||||||
_, err := Verify(email)
|
_, err := Verify(email, nil, nil)
|
||||||
assert.Equal(t, ErrDkimHeaderNotFound, err)
|
assert.Equal(t, ErrDkimHeaderNotFound, err)
|
||||||
|
|
||||||
// No From
|
// No From
|
||||||
email = []byte(signedNoFrom)
|
email = []byte(signedNoFrom)
|
||||||
_, err = Verify(email)
|
_, err = Verify(email, nil, nil)
|
||||||
assert.Equal(t, ErrVerifyBodyHash, err)
|
assert.Equal(t, ErrVerifyBodyHash, err)
|
||||||
|
|
||||||
// missing mandatory 'a' flag
|
// missing mandatory 'a' flag
|
||||||
email = []byte(signedMissingFlag)
|
email = []byte(signedMissingFlag)
|
||||||
_, err = Verify(email)
|
_, err = Verify(email, nil, nil)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.Equal(t, ErrDkimHeaderMissingRequiredTag, err)
|
assert.Equal(t, ErrDkimHeaderMissingRequiredTag, err)
|
||||||
|
|
||||||
// missing bad algo
|
// missing bad algo
|
||||||
email = []byte(signedBadAlgo)
|
email = []byte(signedBadAlgo)
|
||||||
_, err = Verify(email)
|
_, err = Verify(email, nil, nil)
|
||||||
assert.Equal(t, ErrSignBadAlgo, err)
|
assert.Equal(t, ErrSignBadAlgo, err)
|
||||||
|
|
||||||
// relaxed
|
// relaxed
|
||||||
email = []byte(signedRelaxedRelaxedLength)
|
email = []byte(signedRelaxedRelaxedLength)
|
||||||
_, err = Verify(email)
|
_, err = Verify(email, nil, nil)
|
||||||
assert.Equal(t, ErrTesting, err)
|
assert.Equal(t, ErrTesting, err)
|
||||||
|
|
||||||
// simple
|
// simple
|
||||||
email = []byte(signedSimpleSimpleLength)
|
email = []byte(signedSimpleSimpleLength)
|
||||||
_, err = Verify(email)
|
_, err = Verify(email, nil, nil)
|
||||||
assert.Equal(t, ErrTesting, err)
|
assert.Equal(t, ErrTesting, err)
|
||||||
|
|
||||||
// gmail
|
// gmail
|
||||||
email = []byte(fromGmail)
|
email = []byte(fromGmail)
|
||||||
_, err = Verify(email)
|
_, err = Verify(email, nil, nil)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
12
pubKeyRep.go
12
pubKeyRep.go
@@ -4,7 +4,6 @@ import (
|
|||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"net"
|
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -20,16 +19,7 @@ type pubKeyRep struct {
|
|||||||
FlagIMustBeD bool // flag i
|
FlagIMustBeD bool // flag i
|
||||||
}
|
}
|
||||||
|
|
||||||
func newPubKeyFromDnsTxt(selector, domain string) (*pubKeyRep, error) {
|
func newPubKeyFromDnsTxt(txt []string) (*pubKeyRep, error) {
|
||||||
txt, err := net.LookupTXT(selector + "._domainkey." + domain)
|
|
||||||
if err != nil {
|
|
||||||
if strings.HasSuffix(err.Error(), "no such host") {
|
|
||||||
return nil, ErrVerifyNoKeyForSignature
|
|
||||||
} else {
|
|
||||||
return nil, ErrVerifyKeyUnavailable
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// empty record
|
// empty record
|
||||||
if len(txt) == 0 {
|
if len(txt) == 0 {
|
||||||
return nil, ErrVerifyNoKeyForSignature
|
return nil, ErrVerifyNoKeyForSignature
|
||||||
|
|||||||
Reference in New Issue
Block a user