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

View File

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

View File

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