From 016293cce98c148aaef2dd56ba0d0274e192e797 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Depierrepont=20aka=20Toorop?= Date: Thu, 14 May 2015 09:51:58 +0200 Subject: [PATCH] refactoring --- dkim.go | 134 +++++++++++++++++++++------------------------------ dkim_test.go | 2 +- pubKeyRep.go | 2 +- 3 files changed, 57 insertions(+), 81 deletions(-) diff --git a/dkim.go b/dkim.go index 922fbf2..82f3707 100644 --- a/dkim.go +++ b/dkim.go @@ -11,10 +11,7 @@ import ( "crypto/x509" "encoding/base64" "encoding/pem" - //"fmt" "hash" - //"io/ioutil" - //"net" "regexp" "strings" "time" @@ -27,13 +24,16 @@ const ( MaxHeaderLineLength = 70 ) -type VerifyOutput int +type verifyOutput int const ( - SUCCESS VerifyOutput = 1 + iota + SUCCESS verifyOutput = 1 + iota PERMFAIL TEMPFAIL NOTSIGNED + TESTINGSUCCESS + TESTINGPERMFAIL + TESTINGTEMPFAIL ) // sigOptions represents signing options @@ -134,7 +134,6 @@ func Sign(email *[]byte, options sigOptions) error { } // Header must contain "from" - // normalize -> strtlower hasFrom := false for i, h := range options.Headers { h = strings.ToLower(h) @@ -153,20 +152,9 @@ func Sign(email *[]byte, options sigOptions) error { return err } - // hash body - var h2 hash.Hash - var h3 crypto.Hash 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) if err != nil { return err @@ -185,17 +173,12 @@ func Sign(email *[]byte, options sigOptions) error { headers = bytes.TrimRight(headers, " \r\n") // sign - h2.Write(headers) - sig, err := rsa.SignPKCS1v15(rand.Reader, privateKey, h3, h2.Sum(nil)) - if err != nil { - return err - } - sig64 := base64.StdEncoding.EncodeToString(sig) + sig, err := getSignature(&headers, privateKey, signHash[1]) // add to DKIM-Header subh := "" l := len(subh) - for _, c := range sig64 { + for _, c := range sig { subh += string(c) l++ if l >= MaxHeaderLineLength { @@ -213,7 +196,7 @@ func Sign(email *[]byte, options sigOptions) error { // state: SUCCESS or PERMFAIL or TEMPFAIL or NOTSIGNED // msg: a complementary message (if needed) // error: if an error occurs during verification -func Verify(email *[]byte) (VerifyOutput, error) { +func Verify(email *[]byte) (verifyOutput, error) { // parse email dkimHeader, err := NewFromEmail(email) 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 pubKey, verifyOutputOnError, err := newPubKeyFromDnsTxt(dkimHeader.Selector, dkimHeader.Domain) 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 compatible := false for _, algo := range pubKey.HashAlgo { @@ -260,24 +228,56 @@ func Verify(email *[]byte) (VerifyOutput, error) { } } 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 dkimHeaderCano, err := canonicalizeHeader(dkimHeader.RawForSign, strings.Split(dkimHeader.MessageCanonicalization, "/")[0]) if err != nil { - return TEMPFAIL, err + 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 PERMFAIL, err + return getVerifyOutput(PERMFAIL, err, pubKey.FlagTesting) } 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 func canonicalize(email *[]byte, cano string, h []string) (headers, body []byte, err error) { body = []byte{} @@ -289,31 +289,6 @@ func canonicalize(email *[]byte, cano string, h []string) (headers, body []byte, // canonicalyze header 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 // 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 } -/*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 h2 crypto.Hash switch algo { @@ -475,9 +451,9 @@ func getBodyHash(body *[]byte, algo string, bodyLength uint) (string, error) { return "", err } return base64.StdEncoding.EncodeToString(sig), nil +} -}*/ - +// verifySignature verify signature from pubkey func verifySignature(toSign []byte, sig64 string, key *rsa.PublicKey, algo string) error { var h1 hash.Hash var h2 crypto.Hash diff --git a/dkim_test.go b/dkim_test.go index f184384..3f9cf36 100644 --- a/dkim_test.go +++ b/dkim_test.go @@ -268,7 +268,7 @@ func Test_Verify(t *testing.T) { email = []byte(signedNoFrom) status, err = Verify(&email) 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 email = []byte(signedMissingFlag) diff --git a/pubKeyRep.go b/pubKeyRep.go index 2517f96..84f3989 100644 --- a/pubKeyRep.go +++ b/pubKeyRep.go @@ -20,7 +20,7 @@ type pubKeyRep struct { 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) if err != nil { if strings.HasSuffix(err.Error(), "no such host") {