add struct for pubkey representation

This commit is contained in:
Stéphane Depierrepont aka Toorop
2015-05-12 11:59:34 +02:00
parent d652e23632
commit 7206fc7daf
5 changed files with 176 additions and 51 deletions

82
dkim.go
View File

@@ -14,6 +14,7 @@ import (
//"fmt"
"hash"
//"io/ioutil"
//"net"
"regexp"
"strings"
)
@@ -25,6 +26,15 @@ const (
MaxHeaderLineLength = 70
)
type VerifyOutput int
const (
SUCCESS VerifyOutput = 1 + iota
PERMFAIL
TEMPFAIL
NOTSIGNED
)
// sigOptions represents signing options
type sigOptions struct {
@@ -143,31 +153,24 @@ func Sign(email *[]byte, options sigOptions) error {
}
// hash body
var bodyHash string
var h1, h2 hash.Hash
var h2 hash.Hash
var h3 crypto.Hash
signHash := strings.Split(options.Algo, "-")
if signHash[1] == "sha1" {
h1 = sha1.New()
//h1 = sha1.New()
h2 = sha1.New()
h3 = crypto.SHA1
} else {
h1 = sha256.New()
//h1 = sha256.New()
h2 = sha256.New()
h3 = crypto.SHA256
}
// if l tag (body length)
if options.BodyLength != 0 {
if uint(len(body)) < options.BodyLength {
return ErrBadDKimTagLBodyTooShort
}
body = body[0:options.BodyLength]
bodyHash, err := getBodyHash(&body, signHash[1], options.BodyLength)
if err != nil {
return err
}
h1.Write(body)
bodyHash = base64.StdEncoding.EncodeToString(h1.Sum(nil))
// Get dkim header base
dkimHeader := NewDkimHeaderBySigOptions(options)
dHeader := dkimHeader.GetHeaderBaseForSigning(bodyHash)
@@ -209,27 +212,41 @@ 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) (state, msg string, err error) {
func Verify(email *[]byte) (VerifyOutput, error) {
// parse email
dkimHeader, err := NewFromEmail(email)
if err != nil {
if err == ErrDkimHeaderNotFound {
return "NOTSIGNED", ErrDkimHeaderNotFound.Error(), nil
return NOTSIGNED, ErrDkimHeaderNotFound
} else {
return "PERMFAIL", err.Error(), err
return PERMFAIL, err
}
}
println(dkimHeader)
// // Normalize
headers, body, err := canonicalize(email, dkimHeader.MessageCanonicalization, dkimHeader.Headers)
// Normalize
_, body, err := canonicalize(email, dkimHeader.MessageCanonicalization, dkimHeader.Headers)
if err != nil {
return "PERMFAIL", err.Error(), err
return PERMFAIL, err
}
sigHash := strings.Split(dkimHeader.Algorithm, "-")
// expired ? TODO
// get body hash
bodyHash, err := getBodyHash(&body, sigHash[0], dkimHeader.BodyLength)
if err != nil {
return PERMFAIL, err
}
println(bodyHash)
if bodyHash != dkimHeader.BodyHash {
return PERMFAIL, ErrVerifyBodyHash
}
// HERE
// we do not set quesry method because if it's other validation failed earlier
pubKey, err := newPubKeyFromDnsTxt(dkimHeader.Selector, dkimHeader.Domain)
println(pubKey)
return "SUCCESS", "", nil
return SUCCESS, nil
}
// canonicalize returns canonicalized version of header and body
@@ -386,6 +403,27 @@ func canonicalizeHeader(header string, algo string) (string, error) {
return header, ErrSignBadCanonicalization
}
// getBodyHash return the hash (bas64encoded) of the body
func getBodyHash(body *[]byte, algo string, bodyLength uint) (string, error) {
var h hash.Hash
if algo == "sha1" {
h = sha1.New()
} else {
h = sha256.New()
}
toH := *body
// if l tag (body length)
if bodyLength != 0 {
if uint(len(toH)) < bodyLength {
return "", ErrBadDKimTagLBodyTooShort
}
toH = toH[0:bodyLength]
}
h.Write(toH)
return base64.StdEncoding.EncodeToString(h.Sum(nil)), nil
}
// removeFWS removes all FWS from string
func removeFWS(in string) string {
rxReduceWS := regexp.MustCompile(`[ \t]+`)

View File

@@ -2,7 +2,7 @@ package dkim
import (
"bytes"
"errors"
//"errors"
"fmt"
"net/mail"
"net/textproto"
@@ -271,6 +271,7 @@ func NewFromEmail(email *[]byte) (*DkimHeader, error) {
return keep, nil
}
// parseDkHeader parse raw dkim header
func parseDkHeader(header string) (dkh *DkimHeader, err error) {
dkh = new(DkimHeader)
@@ -301,7 +302,7 @@ func parseDkHeader(header string) (dkh *DkimHeader, err error) {
switch flag {
case "v":
if data != "1" {
return nil, ErrDkimVersionUnsuported
return nil, ErrDkimVersionNotsupported
}
dkh.Version = data
mandatoryFlags["v"] = true
@@ -313,27 +314,51 @@ func parseDkHeader(header string) (dkh *DkimHeader, err error) {
mandatoryFlags["a"] = true
case "b":
dkh.SignatureData = removeFWS(data)
mandatoryFlags["b"] = true
if len(dkh.SignatureData) != 0 {
mandatoryFlags["b"] = true
}
case "bh":
dkh.BodyHash = removeFWS(data)
mandatoryFlags["bh"] = true
if len(dkh.BodyHash) != 0 {
mandatoryFlags["bh"] = true
}
case "d":
dkh.Domain = strings.ToLower(data)
mandatoryFlags["d"] = true
if len(dkh.Domain) != 0 {
mandatoryFlags["d"] = true
}
case "h":
data = strings.ToLower(data)
dkh.Headers = strings.Split(data, ":")
mandatoryFlags["h"] = true
if len(dkh.Headers) != 0 {
mandatoryFlags["h"] = true
}
fromFound := false
for _, h := range dkh.Headers {
if h == "from" {
fromFound = true
}
}
if !fromFound {
return nil, ErrDkimHeaderNoFromInHTag
}
case "s":
dkh.Selector = strings.ToLower(data)
mandatoryFlags["s"] = true
if len(dkh.Selector) != 0 {
mandatoryFlags["s"] = true
}
case "c":
dkh.MessageCanonicalization, err = validateCanonicalization(strings.ToLower(data))
if err != nil {
return
return nil, err
}
case "i":
dkh.Auid = data
if data != "" {
if !strings.HasSuffix(data, dkh.Domain) {
return nil, ErrDkimHeaderDomainMismatch
}
dkh.Auid = data
}
case "l":
ui, err := strconv.ParseUint(data, 10, 32)
if err != nil {
@@ -342,6 +367,9 @@ func parseDkHeader(header string) (dkh *DkimHeader, err error) {
dkh.BodyLength = uint(ui)
case "q":
dkh.QueryMethods = strings.Split(data, ":")
if len(dkh.QueryMethods) == 0 || strings.ToLower(dkh.QueryMethods[0]) != "dns/txt" {
return nil, errQueryMethodNotsupported
}
case "t":
ts, err := strconv.ParseInt(data, 10, 64)
if err != nil {
@@ -361,12 +389,22 @@ func parseDkHeader(header string) (dkh *DkimHeader, err error) {
}
// All mandatory flags are in ?
for f, p := range mandatoryFlags {
for _, p := range mandatoryFlags {
if !p {
return nil, errors.New("missing '" + f + "' flag in DKIM header")
return nil, ErrDkimHeaderMissingRequiredTag
}
}
// default for i/Auid
if dkh.Auid == "" {
dkh.Auid = "@" + dkh.Domain
}
// defaut for query method
if len(dkh.QueryMethods) == 0 {
dkh.QueryMethods = []string{"dns/text"}
}
return dkh, nil
}

View File

@@ -112,7 +112,7 @@ var signedSimpleSimple = "DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=simple
" DIpJW4XNA/uqLSswtjCYbJsSg9Ywv1o=" + CRLF + emailBase
var signedNoFrom = "DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=simple/simple;" + CRLF +
" s=test; d=tmail.io; l=5; h=from:date:mime-version:received:received;" + CRLF +
" s=test; d=tmail.io; h=from:date:mime-version:received:received;" + CRLF +
" bh=GF+NsyJx/iX1Yab8k4suJkMG7DBO2lGAB9F2SCY4GWk=;" + CRLF +
" b=SoEhlu1Emm2ASqo8jMhz6FIf2nNHt3ouY4Av/pFFEkQ048RqUFP437ap7RbtL2wh0N3Kkm" + CRLF +
" AKF2TcTLZ++1nalq+djU+/aP4KYQd4RWWFBjkxDzvCH4bvB1M5AGp4Qz9ldmdMQBWOvvSp" + CRLF +
@@ -258,28 +258,38 @@ func Test_Sign(t *testing.T) {
func Test_Verify(t *testing.T) {
// no DKIM header
email := []byte(emailBase)
status, msg, err := Verify(&email)
assert.NoError(t, err)
assert.Equal(t, "NOTSIGNED", status)
assert.Equal(t, ErrDkimHeaderNotFound.Error(), msg)
status, err := Verify(&email)
assert.Equal(t, NOTSIGNED, status)
assert.Equal(t, ErrDkimHeaderNotFound, err)
// No From
email = []byte(signedNoFrom)
status, msg, err = Verify(&email)
assert.NoError(t, err)
assert.Equal(t, "SUCCESS", status)
status, err = Verify(&email)
assert.Equal(t, ErrVerifyBodyHash, err)
assert.Equal(t, PERMFAIL, status) // cause we use dkheader of the "with from" email
// missing mandatory 'a' flag
email = []byte(signedMissingFlag)
status, msg, err = Verify(&email)
status, err = Verify(&email)
assert.Error(t, err)
assert.Equal(t, "PERMFAIL", status)
assert.Equal(t, "missing 'a' flag in DKIM header", msg)
assert.Equal(t, PERMFAIL, status)
assert.Equal(t, ErrDkimHeaderMissingRequiredTag, err)
// missing bad algo
email = []byte(signedBadAlgo)
status, msg, err = Verify(&email)
assert.Error(t, err)
assert.Equal(t, "PERMFAIL", status)
assert.Equal(t, ErrSignBadAlgo.Error(), msg)
status, err = Verify(&email)
assert.Equal(t, PERMFAIL, status)
assert.Equal(t, ErrSignBadAlgo, err)
// relaxed
email = []byte(signedRelaxedRelaxed)
status, err = Verify(&email)
assert.NoError(t, err)
assert.Equal(t, SUCCESS, status)
// simple
email = []byte(signedSimpleSimple)
status, err = Verify(&email)
assert.NoError(t, err)
assert.Equal(t, SUCCESS, status)
}

View File

@@ -15,7 +15,7 @@ var (
ErrSignSelectorRequired = errors.New("Selector is required")
// If Headers is specified it should at least contain 'from'
ErrSignHeaderShouldContainsFrom = errors.New("Header must contains 'from' field")
ErrSignHeaderShouldContainsFrom = errors.New("header must contains 'from' field")
// If bad Canonicalization parameter
ErrSignBadCanonicalization = errors.New("bad Canonicalization parameter")
@@ -41,8 +41,21 @@ var (
// ErrDkimHeaderBTagNotFound when there's no b tag
ErrDkimHeaderBTagNotFound = errors.New("no tag 'b' found in dkim header")
ErrDkimHeaderMissingTagV = errors.New("no tag 'v' found in dkim header")
// ErrDkimHeaderNoFromInHTag
ErrDkimHeaderNoFromInHTag = errors.New("'from' header is missing in h tag")
// ErrDkimHeaderMissingRequiredTag when a required tag is missing
ErrDkimHeaderMissingRequiredTag = errors.New("signature missing required tag")
// ErrDkimHeaderDomainMismatch if i tag is not a sub domain of d tag
ErrDkimHeaderDomainMismatch = errors.New("domain mismatch")
// Version not supported
ErrDkimVersionUnsuported = errors.New("unsuported DKIM version")
ErrDkimVersionNotsupported = errors.New("incompatible version")
// Query method unsopported
errQueryMethodNotsupported = errors.New("query method not supported")
// ErrVerifyBodyHash when body hash doesn't verify
ErrVerifyBodyHash = errors.New("body hash did not verify")
)

26
pubKeyRep.go Normal file
View File

@@ -0,0 +1,26 @@
package dkim
import (
"crypto/rsa"
"fmt"
"net"
)
// pubKeyRep represents a parsed version of public key record
type pubKeyRep struct {
Version string
HashAlgo []string
KeyType string
Note string
PubKey rsa.PublicKey
ServiceType []string
FlagTesting bool // flag y
FlagIMustBeD bool // flag i
}
func newPubKeyFromDnsTxt(selector, domain string) (*pubKeyRep, error) {
txt, err := net.LookupTXT(selector + "._domainkey." + domain)
fmt.Println(txt, err)
return nil, nil
}