refactoring

This commit is contained in:
Stéphane Depierrepont aka Toorop
2015-05-14 09:51:58 +02:00
parent dc14f06d84
commit 016293cce9
3 changed files with 57 additions and 81 deletions

134
dkim.go
View File

@@ -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

View File

@@ -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)

View File

@@ -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") {