refactoring
This commit is contained in:
134
dkim.go
134
dkim.go
@@ -11,10 +11,7 @@ import (
|
|||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
//"fmt"
|
|
||||||
"hash"
|
"hash"
|
||||||
//"io/ioutil"
|
|
||||||
//"net"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -27,13 +24,16 @@ const (
|
|||||||
MaxHeaderLineLength = 70
|
MaxHeaderLineLength = 70
|
||||||
)
|
)
|
||||||
|
|
||||||
type VerifyOutput int
|
type verifyOutput int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
SUCCESS VerifyOutput = 1 + iota
|
SUCCESS verifyOutput = 1 + iota
|
||||||
PERMFAIL
|
PERMFAIL
|
||||||
TEMPFAIL
|
TEMPFAIL
|
||||||
NOTSIGNED
|
NOTSIGNED
|
||||||
|
TESTINGSUCCESS
|
||||||
|
TESTINGPERMFAIL
|
||||||
|
TESTINGTEMPFAIL
|
||||||
)
|
)
|
||||||
|
|
||||||
// sigOptions represents signing options
|
// sigOptions represents signing options
|
||||||
@@ -134,7 +134,6 @@ func Sign(email *[]byte, options sigOptions) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Header must contain "from"
|
// Header must contain "from"
|
||||||
// normalize -> strtlower
|
|
||||||
hasFrom := false
|
hasFrom := false
|
||||||
for i, h := range options.Headers {
|
for i, h := range options.Headers {
|
||||||
h = strings.ToLower(h)
|
h = strings.ToLower(h)
|
||||||
@@ -153,20 +152,9 @@ func Sign(email *[]byte, options sigOptions) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// hash body
|
|
||||||
var h2 hash.Hash
|
|
||||||
var h3 crypto.Hash
|
|
||||||
signHash := strings.Split(options.Algo, "-")
|
signHash := strings.Split(options.Algo, "-")
|
||||||
if signHash[1] == "sha1" {
|
|
||||||
//h1 = sha1.New()
|
|
||||||
h2 = sha1.New()
|
|
||||||
h3 = crypto.SHA1
|
|
||||||
} else {
|
|
||||||
//h1 = sha256.New()
|
|
||||||
h2 = sha256.New()
|
|
||||||
h3 = crypto.SHA256
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// hash body
|
||||||
bodyHash, err := getBodyHash(&body, signHash[1], options.BodyLength)
|
bodyHash, err := getBodyHash(&body, signHash[1], options.BodyLength)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -185,17 +173,12 @@ func Sign(email *[]byte, options sigOptions) error {
|
|||||||
headers = bytes.TrimRight(headers, " \r\n")
|
headers = bytes.TrimRight(headers, " \r\n")
|
||||||
|
|
||||||
// sign
|
// sign
|
||||||
h2.Write(headers)
|
sig, err := getSignature(&headers, privateKey, signHash[1])
|
||||||
sig, err := rsa.SignPKCS1v15(rand.Reader, privateKey, h3, h2.Sum(nil))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
sig64 := base64.StdEncoding.EncodeToString(sig)
|
|
||||||
|
|
||||||
// add to DKIM-Header
|
// add to DKIM-Header
|
||||||
subh := ""
|
subh := ""
|
||||||
l := len(subh)
|
l := len(subh)
|
||||||
for _, c := range sig64 {
|
for _, c := range sig {
|
||||||
subh += string(c)
|
subh += string(c)
|
||||||
l++
|
l++
|
||||||
if l >= MaxHeaderLineLength {
|
if l >= MaxHeaderLineLength {
|
||||||
@@ -213,7 +196,7 @@ func Sign(email *[]byte, options sigOptions) error {
|
|||||||
// state: SUCCESS or PERMFAIL or TEMPFAIL or NOTSIGNED
|
// state: SUCCESS or PERMFAIL or TEMPFAIL or NOTSIGNED
|
||||||
// msg: a complementary message (if needed)
|
// msg: a complementary message (if needed)
|
||||||
// error: if an error occurs during verification
|
// error: if an error occurs during verification
|
||||||
func Verify(email *[]byte) (VerifyOutput, error) {
|
func Verify(email *[]byte) (verifyOutput, error) {
|
||||||
// parse email
|
// parse email
|
||||||
dkimHeader, err := NewFromEmail(email)
|
dkimHeader, err := NewFromEmail(email)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -224,33 +207,18 @@ func Verify(email *[]byte) (VerifyOutput, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normalize
|
|
||||||
headers, body, err := canonicalize(email, dkimHeader.MessageCanonicalization, dkimHeader.Headers)
|
|
||||||
if err != nil {
|
|
||||||
return PERMFAIL, err
|
|
||||||
}
|
|
||||||
sigHash := strings.Split(dkimHeader.Algorithm, "-")
|
|
||||||
|
|
||||||
// expired ?
|
|
||||||
if !dkimHeader.SignatureExpiration.IsZero() && dkimHeader.SignatureExpiration.Second() < time.Now().Second() {
|
|
||||||
return PERMFAIL, ErrVerifySignatureHasExpired
|
|
||||||
}
|
|
||||||
|
|
||||||
// get body hash
|
|
||||||
bodyHash, err := getBodyHash(&body, sigHash[1], dkimHeader.BodyLength)
|
|
||||||
if err != nil {
|
|
||||||
return PERMFAIL, err
|
|
||||||
}
|
|
||||||
if bodyHash != dkimHeader.BodyHash {
|
|
||||||
return PERMFAIL, ErrVerifyBodyHash
|
|
||||||
}
|
|
||||||
|
|
||||||
// we do not set query method because if it's others, validation failed earlier
|
// we do not set query method because if it's others, validation failed earlier
|
||||||
pubKey, verifyOutputOnError, err := newPubKeyFromDnsTxt(dkimHeader.Selector, dkimHeader.Domain)
|
pubKey, verifyOutputOnError, err := newPubKeyFromDnsTxt(dkimHeader.Selector, dkimHeader.Domain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return verifyOutputOnError, err
|
return getVerifyOutput(verifyOutputOnError, err, pubKey.FlagTesting)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Normalize
|
||||||
|
headers, body, err := canonicalize(email, dkimHeader.MessageCanonicalization, dkimHeader.Headers)
|
||||||
|
if err != nil {
|
||||||
|
return getVerifyOutput(PERMFAIL, err, pubKey.FlagTesting)
|
||||||
|
}
|
||||||
|
sigHash := strings.Split(dkimHeader.Algorithm, "-")
|
||||||
// check if hash algo are compatible
|
// check if hash algo are compatible
|
||||||
compatible := false
|
compatible := false
|
||||||
for _, algo := range pubKey.HashAlgo {
|
for _, algo := range pubKey.HashAlgo {
|
||||||
@@ -260,24 +228,56 @@ func Verify(email *[]byte) (VerifyOutput, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !compatible {
|
if !compatible {
|
||||||
return PERMFAIL, ErrVerifyInappropriateHashAlgo
|
return getVerifyOutput(PERMFAIL, ErrVerifyInappropriateHashAlgo, pubKey.FlagTesting)
|
||||||
|
}
|
||||||
|
|
||||||
|
// expired ?
|
||||||
|
if !dkimHeader.SignatureExpiration.IsZero() && dkimHeader.SignatureExpiration.Second() < time.Now().Second() {
|
||||||
|
return getVerifyOutput(PERMFAIL, ErrVerifySignatureHasExpired, pubKey.FlagTesting)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// get body hash
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
// compute sig
|
// compute sig
|
||||||
dkimHeaderCano, err := canonicalizeHeader(dkimHeader.RawForSign, strings.Split(dkimHeader.MessageCanonicalization, "/")[0])
|
dkimHeaderCano, err := canonicalizeHeader(dkimHeader.RawForSign, strings.Split(dkimHeader.MessageCanonicalization, "/")[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return TEMPFAIL, err
|
return getVerifyOutput(TEMPFAIL, err, pubKey.FlagTesting)
|
||||||
}
|
}
|
||||||
toSignStr := string(headers) + dkimHeaderCano
|
toSignStr := string(headers) + dkimHeaderCano
|
||||||
toSign := bytes.TrimRight([]byte(toSignStr), " \r\n")
|
toSign := bytes.TrimRight([]byte(toSignStr), " \r\n")
|
||||||
|
|
||||||
err = verifySignature(toSign, dkimHeader.SignatureData, &pubKey.PubKey, sigHash[1])
|
err = verifySignature(toSign, dkimHeader.SignatureData, &pubKey.PubKey, sigHash[1])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return PERMFAIL, err
|
return getVerifyOutput(PERMFAIL, err, pubKey.FlagTesting)
|
||||||
}
|
}
|
||||||
return SUCCESS, nil
|
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
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
// canonicalize returns canonicalized version of header and body
|
// 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{}
|
body = []byte{}
|
||||||
@@ -289,31 +289,6 @@ func canonicalize(email *[]byte, cano string, h []string) (headers, body []byte,
|
|||||||
|
|
||||||
// canonicalyze header
|
// canonicalyze header
|
||||||
headersList, err := getHeadersList(&rawHeaders)
|
headersList, err := getHeadersList(&rawHeaders)
|
||||||
/*headersList := list.New()
|
|
||||||
currentHeader := []byte{}
|
|
||||||
for _, line := range bytes.SplitAfter(parts[0], []byte{10}) {
|
|
||||||
if line[0] == 32 || line[0] == 9 {
|
|
||||||
if len(currentHeader) == 0 {
|
|
||||||
return headers, body, ErrBadMailFormatHeaders
|
|
||||||
}
|
|
||||||
currentHeader = append(currentHeader, line...)
|
|
||||||
} else {
|
|
||||||
// New header, save current if exists
|
|
||||||
if len(currentHeader) != 0 {
|
|
||||||
headersList.PushBack(string(currentHeader))
|
|
||||||
currentHeader = []byte{}
|
|
||||||
|
|
||||||
}
|
|
||||||
currentHeader = append(currentHeader, line...)
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
|
|
||||||
// debug
|
|
||||||
/*fmt.Println("-------------------------------------------")
|
|
||||||
for e := headersList.Front(); e != nil; e = e.Next() {
|
|
||||||
fmt.Printf("|%s|\n", e.Value.(string))
|
|
||||||
}
|
|
||||||
fmt.Println("-------------------------------------------")*/
|
|
||||||
|
|
||||||
// pour chaque header a conserver on traverse tous les headers dispo
|
// pour chaque header a conserver on traverse tous les headers dispo
|
||||||
// If multi instance of a field we must keep it from the bottom to the top
|
// If multi instance of a field we must keep it from the bottom to the top
|
||||||
@@ -452,7 +427,8 @@ func getBodyHash(body *[]byte, algo string, bodyLength uint) (string, error) {
|
|||||||
return base64.StdEncoding.EncodeToString(h.Sum(nil)), nil
|
return base64.StdEncoding.EncodeToString(h.Sum(nil)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
/*func getSignature(toSign *[]byte, key *rsa.PrivateKey, algo string) (string, error) {
|
// getSignature return signature of toSign using key
|
||||||
|
func getSignature(toSign *[]byte, key *rsa.PrivateKey, algo string) (string, error) {
|
||||||
var h1 hash.Hash
|
var h1 hash.Hash
|
||||||
var h2 crypto.Hash
|
var h2 crypto.Hash
|
||||||
switch algo {
|
switch algo {
|
||||||
@@ -475,9 +451,9 @@ func getBodyHash(body *[]byte, algo string, bodyLength uint) (string, error) {
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return base64.StdEncoding.EncodeToString(sig), nil
|
return base64.StdEncoding.EncodeToString(sig), nil
|
||||||
|
}
|
||||||
|
|
||||||
}*/
|
// verifySignature verify signature from pubkey
|
||||||
|
|
||||||
func verifySignature(toSign []byte, sig64 string, key *rsa.PublicKey, algo string) error {
|
func verifySignature(toSign []byte, sig64 string, key *rsa.PublicKey, algo string) error {
|
||||||
var h1 hash.Hash
|
var h1 hash.Hash
|
||||||
var h2 crypto.Hash
|
var h2 crypto.Hash
|
||||||
|
|||||||
@@ -268,7 +268,7 @@ func Test_Verify(t *testing.T) {
|
|||||||
email = []byte(signedNoFrom)
|
email = []byte(signedNoFrom)
|
||||||
status, err = Verify(&email)
|
status, err = Verify(&email)
|
||||||
assert.Equal(t, ErrVerifyBodyHash, err)
|
assert.Equal(t, ErrVerifyBodyHash, err)
|
||||||
assert.Equal(t, PERMFAIL, status) // cause we use dkheader of the "with from" email
|
assert.Equal(t, TESTINGPERMFAIL, status) // cause we use dkheader of the "with from" email
|
||||||
|
|
||||||
// missing mandatory 'a' flag
|
// missing mandatory 'a' flag
|
||||||
email = []byte(signedMissingFlag)
|
email = []byte(signedMissingFlag)
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ type pubKeyRep struct {
|
|||||||
FlagIMustBeD bool // flag i
|
FlagIMustBeD bool // flag i
|
||||||
}
|
}
|
||||||
|
|
||||||
func newPubKeyFromDnsTxt(selector, domain string) (*pubKeyRep, VerifyOutput, error) {
|
func newPubKeyFromDnsTxt(selector, domain string) (*pubKeyRep, verifyOutput, error) {
|
||||||
txt, err := net.LookupTXT(selector + "._domainkey." + domain)
|
txt, err := net.LookupTXT(selector + "._domainkey." + domain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.HasSuffix(err.Error(), "no such host") {
|
if strings.HasSuffix(err.Error(), "no such host") {
|
||||||
|
|||||||
Reference in New Issue
Block a user