verify 1
This commit is contained in:
91
dkim.go
91
dkim.go
@@ -68,14 +68,14 @@ type sigOptions struct {
|
|||||||
SignatureExpireIn uint64
|
SignatureExpireIn uint64
|
||||||
|
|
||||||
// CopiedHeaderFileds
|
// CopiedHeaderFileds
|
||||||
CopiedHeaderFileds []string
|
CopiedHeaderFields []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSigOption returns new sigoption with some defaults value
|
// NewSigOption returns new sigoption with some defaults value
|
||||||
func NewSigOptions() sigOptions {
|
func NewSigOptions() sigOptions {
|
||||||
return sigOptions{
|
return sigOptions{
|
||||||
Version: 1,
|
Version: 1,
|
||||||
Canonicalization: "relaxed/simple",
|
Canonicalization: "simple/simple",
|
||||||
Algo: "rsa-sha256",
|
Algo: "rsa-sha256",
|
||||||
Headers: []string{"from"},
|
Headers: []string{"from"},
|
||||||
BodyLength: 0,
|
BodyLength: 0,
|
||||||
@@ -111,18 +111,9 @@ func Sign(email *[]byte, options sigOptions) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Canonicalization
|
// Canonicalization
|
||||||
options.Canonicalization = strings.ToLower(options.Canonicalization)
|
options.Canonicalization, err = validateCanonicalization(strings.ToLower(options.Canonicalization))
|
||||||
p := strings.Split(options.Canonicalization, "/")
|
if err != nil {
|
||||||
if len(p) > 2 {
|
return err
|
||||||
return ErrSignBadCanonicalization
|
|
||||||
}
|
|
||||||
if len(p) == 1 {
|
|
||||||
options.Canonicalization = options.Canonicalization + "/simple"
|
|
||||||
}
|
|
||||||
for _, c := range p {
|
|
||||||
if c != "simple" && c != "relaxed" {
|
|
||||||
return ErrSignBadCanonicalization
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Algo
|
// Algo
|
||||||
@@ -146,7 +137,7 @@ func Sign(email *[]byte, options sigOptions) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Normalize
|
// Normalize
|
||||||
headers, body, err := canonicalize(email, options)
|
headers, body, err := canonicalize(email, options.Canonicalization, options.Headers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -179,7 +170,7 @@ func Sign(email *[]byte, options sigOptions) error {
|
|||||||
|
|
||||||
// Get dkim header base
|
// Get dkim header base
|
||||||
dkimHeader := NewDkimHeaderBySigOptions(options)
|
dkimHeader := NewDkimHeaderBySigOptions(options)
|
||||||
dHeader := dkimHeader.GetHeaderBase(bodyHash)
|
dHeader := dkimHeader.GetHeaderBaseForSigning(bodyHash)
|
||||||
|
|
||||||
canonicalizations := strings.Split(options.Canonicalization, "/")
|
canonicalizations := strings.Split(options.Canonicalization, "/")
|
||||||
dHeaderCanonicalized, err := canonicalizeHeader(dHeader, canonicalizations[0])
|
dHeaderCanonicalized, err := canonicalizeHeader(dHeader, canonicalizations[0])
|
||||||
@@ -214,8 +205,35 @@ func Sign(email *[]byte, options sigOptions) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// verify verifies an email an return
|
||||||
|
// 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) {
|
||||||
|
// parse email
|
||||||
|
dkimHeader, err := NewFromEmail(email)
|
||||||
|
if err != nil {
|
||||||
|
if err == ErrDkimHeaderNotFound {
|
||||||
|
return "NOTSIGNED", ErrDkimHeaderNotFound.Error(), nil
|
||||||
|
} else {
|
||||||
|
return "PERMFAIL", err.Error(), err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println(dkimHeader)
|
||||||
|
|
||||||
|
// // Normalize
|
||||||
|
headers, body, err := canonicalize(email, dkimHeader.MessageCanonicalization, dkimHeader.Headers)
|
||||||
|
if err != nil {
|
||||||
|
return "PERMFAIL", err.Error(), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// HERE
|
||||||
|
|
||||||
|
return "SUCCESS", "", nil
|
||||||
|
}
|
||||||
|
|
||||||
// canonicalize returns canonicalized version of header and body
|
// canonicalize returns canonicalized version of header and body
|
||||||
func canonicalize(email *[]byte, options sigOptions) (headers, body []byte, err error) {
|
func canonicalize(email *[]byte, cano string, h []string) (headers, body []byte, err error) {
|
||||||
body = []byte{}
|
body = []byte{}
|
||||||
rxReduceWS := regexp.MustCompile(`[ \t]+`)
|
rxReduceWS := regexp.MustCompile(`[ \t]+`)
|
||||||
|
|
||||||
@@ -231,7 +249,7 @@ func canonicalize(email *[]byte, options sigOptions) (headers, body []byte, err
|
|||||||
parts[1] = []byte{13, 10}
|
parts[1] = []byte{13, 10}
|
||||||
}
|
}
|
||||||
|
|
||||||
canonicalizations := strings.Split(options.Canonicalization, "/")
|
canonicalizations := strings.Split(cano, "/")
|
||||||
|
|
||||||
// canonicalyze header
|
// canonicalyze header
|
||||||
headersList := list.New()
|
headersList := list.New()
|
||||||
@@ -258,7 +276,7 @@ func canonicalize(email *[]byte, options sigOptions) (headers, body []byte, err
|
|||||||
var match *list.Element
|
var match *list.Element
|
||||||
headersToKeepList := list.New()
|
headersToKeepList := list.New()
|
||||||
|
|
||||||
for _, headerToKeep := range options.Headers {
|
for _, headerToKeep := range h {
|
||||||
match = nil
|
match = nil
|
||||||
headerToKeepToLower := strings.ToLower(headerToKeep)
|
headerToKeepToLower := strings.ToLower(headerToKeep)
|
||||||
for e := headersList.Front(); e != nil; e = e.Next() {
|
for e := headersList.Front(); e != nil; e = e.Next() {
|
||||||
@@ -323,7 +341,7 @@ func canonicalize(email *[]byte, options sigOptions) (headers, body []byte, err
|
|||||||
|
|
||||||
// canonicalizeHeader returns canonicalized version of header
|
// canonicalizeHeader returns canonicalized version of header
|
||||||
func canonicalizeHeader(header string, algo string) (string, error) {
|
func canonicalizeHeader(header string, algo string) (string, error) {
|
||||||
rxReduceWS := regexp.MustCompile(`[ \t]+`)
|
//rxReduceWS := regexp.MustCompile(`[ \t]+`)
|
||||||
if algo == "simple" {
|
if algo == "simple" {
|
||||||
// The "simple" header canonicalization algorithm does not change header
|
// The "simple" header canonicalization algorithm does not change header
|
||||||
// fields in any way. Header fields MUST be presented to the signing or
|
// fields in any way. Header fields MUST be presented to the signing or
|
||||||
@@ -360,11 +378,36 @@ func canonicalizeHeader(header string, algo string) (string, error) {
|
|||||||
}
|
}
|
||||||
k := strings.ToLower(kv[0])
|
k := strings.ToLower(kv[0])
|
||||||
k = strings.TrimSpace(k)
|
k = strings.TrimSpace(k)
|
||||||
v := strings.Replace(kv[1], "\n", "", -1)
|
v := removeFWS(kv[1])
|
||||||
v = strings.Replace(v, "\r", "", -1)
|
//v = rxReduceWS.ReplaceAllString(v, " ")
|
||||||
v = rxReduceWS.ReplaceAllString(v, " ")
|
//v = strings.TrimSpace(v)
|
||||||
v = strings.TrimSpace(v)
|
|
||||||
return k + ":" + v + CRLF, nil
|
return k + ":" + v + CRLF, nil
|
||||||
}
|
}
|
||||||
return header, ErrSignBadCanonicalization
|
return header, ErrSignBadCanonicalization
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// removeFWS removes all FWS from string
|
||||||
|
func removeFWS(in string) string {
|
||||||
|
rxReduceWS := regexp.MustCompile(`[ \t]+`)
|
||||||
|
out := strings.Replace(in, "\n", "", -1)
|
||||||
|
out = strings.Replace(out, "\r", "", -1)
|
||||||
|
out = rxReduceWS.ReplaceAllString(out, " ")
|
||||||
|
return strings.TrimSpace(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateCanonicalization validate canonicalization (c flag)
|
||||||
|
func validateCanonicalization(cano string) (string, error) {
|
||||||
|
p := strings.Split(cano, "/")
|
||||||
|
if len(p) > 2 {
|
||||||
|
return "", ErrSignBadCanonicalization
|
||||||
|
}
|
||||||
|
if len(p) == 1 {
|
||||||
|
cano = cano + "/simple"
|
||||||
|
}
|
||||||
|
for _, c := range p {
|
||||||
|
if c != "simple" && c != "relaxed" {
|
||||||
|
return "", ErrSignBadCanonicalization
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cano, nil
|
||||||
|
}
|
||||||
|
|||||||
172
dkimHeader.go
172
dkimHeader.go
@@ -1,7 +1,12 @@
|
|||||||
package dkim
|
package dkim
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/mail"
|
||||||
|
"net/textproto"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -180,7 +185,16 @@ type DkimHeader struct {
|
|||||||
// in the "z=" tag. Copied header field values are for diagnostic
|
// in the "z=" tag. Copied header field values are for diagnostic
|
||||||
// use.
|
// use.
|
||||||
// tag z
|
// tag z
|
||||||
CopiedHeaderFileds []string
|
CopiedHeaderFields []string
|
||||||
|
|
||||||
|
// HeaderMailFromDomain store the raw email address of the header Mail From
|
||||||
|
// used for verifying in case of multiple DKIM header (we will prioritise
|
||||||
|
// header with d = mail from domain)
|
||||||
|
//HeaderMailFromDomain string
|
||||||
|
|
||||||
|
// RawForsign represents the raw part (with non canonicalization) of the header
|
||||||
|
// used for computint sig in verify process
|
||||||
|
RawForSign string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDkimHeaderBySigOptions return a new DkimHeader initioalized with sigOptions value
|
// NewDkimHeaderBySigOptions return a new DkimHeader initioalized with sigOptions value
|
||||||
@@ -201,13 +215,165 @@ func NewDkimHeaderBySigOptions(options sigOptions) *DkimHeader {
|
|||||||
if options.SignatureExpireIn > 0 {
|
if options.SignatureExpireIn > 0 {
|
||||||
h.SignatureExpiration = time.Now().Add(time.Duration(options.SignatureExpireIn) * time.Second)
|
h.SignatureExpiration = time.Now().Add(time.Duration(options.SignatureExpireIn) * time.Second)
|
||||||
}
|
}
|
||||||
h.CopiedHeaderFileds = options.CopiedHeaderFileds
|
h.CopiedHeaderFields = options.CopiedHeaderFields
|
||||||
return h
|
return h
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewFromEmail return a new DkimHeader by parsing an email
|
||||||
|
// Note: according to RFC 6376 an email can have multiple DKIM Header
|
||||||
|
// in this case we return the last inserted or the last with d== mail from
|
||||||
|
func NewFromEmail(email *[]byte) (*DkimHeader, error) {
|
||||||
|
m, err := mail.ReadMessage(bytes.NewReader(*email))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// DKIM header ?
|
||||||
|
if len(m.Header[textproto.CanonicalMIMEHeaderKey("DKIM-Signature")]) == 0 {
|
||||||
|
return nil, ErrDkimHeaderNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get mail from domain
|
||||||
|
mailFromDomain := ""
|
||||||
|
mailfrom, err := mail.ParseAddress(m.Header.Get(textproto.CanonicalMIMEHeaderKey("From")))
|
||||||
|
if err != nil {
|
||||||
|
if err.Error() != "mail: no address" {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t := strings.SplitAfter(mailfrom.Address, "@")
|
||||||
|
if len(t) > 1 {
|
||||||
|
mailFromDomain = strings.ToLower(t[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var keep *DkimHeader
|
||||||
|
var keepErr error
|
||||||
|
for _, dk := range m.Header[textproto.CanonicalMIMEHeaderKey("DKIM-Signature")] {
|
||||||
|
parsed, err := parseDkHeader(dk)
|
||||||
|
// if malformed dkim header try next
|
||||||
|
if err != nil {
|
||||||
|
keepErr = err
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Keep first dkim headers
|
||||||
|
if keep == nil {
|
||||||
|
keep = parsed
|
||||||
|
}
|
||||||
|
// if d flag == domain keep this header and return
|
||||||
|
if mailFromDomain == parsed.Domain {
|
||||||
|
return parsed, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if keep == nil {
|
||||||
|
return nil, keepErr
|
||||||
|
}
|
||||||
|
return keep, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseDkHeader(header string) (dkh *DkimHeader, err error) {
|
||||||
|
dkh = new(DkimHeader)
|
||||||
|
|
||||||
|
t := strings.LastIndex(header, "b=")
|
||||||
|
if t == -1 {
|
||||||
|
return nil, ErrDkimHeaderBTagNotFound
|
||||||
|
}
|
||||||
|
dkh.RawForSign = header[0 : t+2]
|
||||||
|
// Mandatory
|
||||||
|
mandatoryFlags := make(map[string]bool, 7) //(b'v', b'a', b'b', b'bh', b'd', b'h', b's')
|
||||||
|
mandatoryFlags["v"] = false
|
||||||
|
mandatoryFlags["a"] = false
|
||||||
|
mandatoryFlags["b"] = false
|
||||||
|
mandatoryFlags["bh"] = false
|
||||||
|
mandatoryFlags["d"] = false
|
||||||
|
mandatoryFlags["h"] = false
|
||||||
|
mandatoryFlags["s"] = false
|
||||||
|
|
||||||
|
// default values
|
||||||
|
dkh.MessageCanonicalization = "simple/simple"
|
||||||
|
dkh.QueryMethods = []string{"dns/txt"}
|
||||||
|
|
||||||
|
fs := strings.Split(header, ";")
|
||||||
|
for _, f := range fs {
|
||||||
|
flagData := strings.SplitN(f, "=", 2)
|
||||||
|
flag := strings.ToLower(strings.TrimSpace(flagData[0]))
|
||||||
|
data := strings.TrimSpace(flagData[1])
|
||||||
|
switch flag {
|
||||||
|
case "v":
|
||||||
|
if data != "1" {
|
||||||
|
return nil, ErrDkimVersionUnsuported
|
||||||
|
}
|
||||||
|
dkh.Version = data
|
||||||
|
mandatoryFlags["v"] = true
|
||||||
|
case "a":
|
||||||
|
dkh.Algorithm = strings.ToLower(data)
|
||||||
|
if dkh.Algorithm != "rsa-sha1" && dkh.Algorithm != "rsa-sha256" {
|
||||||
|
return nil, ErrSignBadAlgo
|
||||||
|
}
|
||||||
|
mandatoryFlags["a"] = true
|
||||||
|
case "b":
|
||||||
|
dkh.SignatureData = removeFWS(data)
|
||||||
|
mandatoryFlags["b"] = true
|
||||||
|
case "bh":
|
||||||
|
dkh.BodyHash = removeFWS(data)
|
||||||
|
mandatoryFlags["bh"] = true
|
||||||
|
case "d":
|
||||||
|
dkh.Domain = strings.ToLower(data)
|
||||||
|
mandatoryFlags["d"] = true
|
||||||
|
case "h":
|
||||||
|
data = strings.ToLower(data)
|
||||||
|
dkh.Headers = strings.Split(data, ":")
|
||||||
|
mandatoryFlags["h"] = true
|
||||||
|
case "s":
|
||||||
|
dkh.Selector = strings.ToLower(data)
|
||||||
|
mandatoryFlags["s"] = true
|
||||||
|
case "c":
|
||||||
|
dkh.MessageCanonicalization, err = validateCanonicalization(strings.ToLower(data))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "i":
|
||||||
|
dkh.Auid = data
|
||||||
|
case "l":
|
||||||
|
ui, err := strconv.ParseUint(data, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
dkh.BodyLength = uint(ui)
|
||||||
|
case "q":
|
||||||
|
dkh.QueryMethods = strings.Split(data, ":")
|
||||||
|
case "t":
|
||||||
|
ts, err := strconv.ParseInt(data, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
dkh.SignatureTimestamp = time.Unix(ts, 0)
|
||||||
|
|
||||||
|
case "x":
|
||||||
|
ts, err := strconv.ParseInt(data, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
dkh.SignatureExpiration = time.Unix(ts, 0)
|
||||||
|
case "z":
|
||||||
|
dkh.CopiedHeaderFields = strings.Split(data, "|")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// All mandatory flags are in ?
|
||||||
|
for f, p := range mandatoryFlags {
|
||||||
|
if !p {
|
||||||
|
return nil, errors.New("missing '" + f + "' flag in DKIM header")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dkh, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// GetHeaderBase return base header for signers
|
// GetHeaderBase return base header for signers
|
||||||
// Todo: some refactoring needed...
|
// Todo: some refactoring needed...
|
||||||
func (d *DkimHeader) GetHeaderBase(bodyHash string) string {
|
func (d *DkimHeader) GetHeaderBaseForSigning(bodyHash string) string {
|
||||||
h := "DKIM-Signature: v=" + d.Version + "; a=" + d.Algorithm + "; q=" + strings.Join(d.QueryMethods, ":") + "; c=" + d.MessageCanonicalization + ";" + CRLF + TAB
|
h := "DKIM-Signature: v=" + d.Version + "; a=" + d.Algorithm + "; q=" + strings.Join(d.QueryMethods, ":") + "; c=" + d.MessageCanonicalization + ";" + CRLF + TAB
|
||||||
subh := "s=" + d.Selector + ";"
|
subh := "s=" + d.Selector + ";"
|
||||||
if len(subh)+len(d.Domain)+4 > MaxHeaderLineLength {
|
if len(subh)+len(d.Domain)+4 > MaxHeaderLineLength {
|
||||||
|
|||||||
82
dkim_test.go
82
dkim_test.go
@@ -52,6 +52,23 @@ var emailBase = "Received: (qmail 28277 invoked from network); 1 May 2015 09:43:
|
|||||||
"-- " + CRLF +
|
"-- " + CRLF +
|
||||||
"Toorop" + CRLF + CRLF + CRLF + CRLF + CRLF + CRLF
|
"Toorop" + CRLF + CRLF + CRLF + CRLF + CRLF + CRLF
|
||||||
|
|
||||||
|
var emailBaseNoFrom = "Received: (qmail 28277 invoked from network); 1 May 2015 09:43:37 -0000" + CRLF +
|
||||||
|
"Received: (qmail 21323 invoked from network); 1 May 2015 09:48:39 -0000" + CRLF +
|
||||||
|
"Received: from mail483.ha.ovh.net (b6.ovh.net [213.186.33.56])" + CRLF +
|
||||||
|
" by mo51.mail-out.ovh.net (Postfix) with SMTP id A6E22FF8934" + CRLF +
|
||||||
|
" for <toorop@toorop.fr>; Mon, 4 May 2015 14:00:47 +0200 (CEST)" + CRLF +
|
||||||
|
"MIME-Version: 1.0" + CRLF +
|
||||||
|
"Date: Fri, 1 May 2015 11:48:37 +0200" + CRLF +
|
||||||
|
"Message-ID: <CADu37kTXBeNkJdXc4bSF8DbJnXmNjkLbnswK6GzG_2yn7U7P6w@tmail.io>" + CRLF +
|
||||||
|
"Subject: Test DKIM" + CRLF +
|
||||||
|
"To: =?UTF-8?Q?St=C3=A9phane_Depierrepont?= <toorop@toorop.fr>" + CRLF +
|
||||||
|
"Content-Type: text/plain; charset=UTF-8" + CRLF + CRLF +
|
||||||
|
"Hello world" + CRLF +
|
||||||
|
"line with trailing space " + CRLF +
|
||||||
|
"line with space " + CRLF +
|
||||||
|
"-- " + CRLF +
|
||||||
|
"Toorop" + CRLF + CRLF + CRLF + CRLF + CRLF + CRLF
|
||||||
|
|
||||||
var headerSimple = "From: =?UTF-8?Q?St=C3=A9phane_Depierrepont?= <toorop@tmail.io>" + CRLF +
|
var headerSimple = "From: =?UTF-8?Q?St=C3=A9phane_Depierrepont?= <toorop@tmail.io>" + CRLF +
|
||||||
"Date: Fri, 1 May 2015 11:48:37 +0200" + CRLF +
|
"Date: Fri, 1 May 2015 11:48:37 +0200" + CRLF +
|
||||||
"MIME-Version: 1.0" + CRLF +
|
"MIME-Version: 1.0" + CRLF +
|
||||||
@@ -94,10 +111,44 @@ var signedSimpleSimple = "DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=simple
|
|||||||
" AKF2TcTLZ++1nalq+djU+/aP4KYQd4RWWFBjkxDzvCH4bvB1M5AGp4Qz9ldmdMQBWOvvSp" + CRLF +
|
" AKF2TcTLZ++1nalq+djU+/aP4KYQd4RWWFBjkxDzvCH4bvB1M5AGp4Qz9ldmdMQBWOvvSp" + CRLF +
|
||||||
" DIpJW4XNA/uqLSswtjCYbJsSg9Ywv1o=" + CRLF + emailBase
|
" 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 +
|
||||||
|
" bh=GF+NsyJx/iX1Yab8k4suJkMG7DBO2lGAB9F2SCY4GWk=;" + CRLF +
|
||||||
|
" b=SoEhlu1Emm2ASqo8jMhz6FIf2nNHt3ouY4Av/pFFEkQ048RqUFP437ap7RbtL2wh0N3Kkm" + CRLF +
|
||||||
|
" AKF2TcTLZ++1nalq+djU+/aP4KYQd4RWWFBjkxDzvCH4bvB1M5AGp4Qz9ldmdMQBWOvvSp" + CRLF +
|
||||||
|
" DIpJW4XNA/uqLSswtjCYbJsSg9Ywv1o=" + CRLF + emailBaseNoFrom
|
||||||
|
|
||||||
|
var signedMissingFlag = "DKIM-Signature: v=1; q=dns/txt; c=simple/simple;" + CRLF +
|
||||||
|
" s=test; d=tmail.io; l=5; h=from:date:mime-version:received:received;" + CRLF +
|
||||||
|
" bh=GF+NsyJx/iX1Yab8k4suJkMG7DBO2lGAB9F2SCY4GWk=;" + CRLF +
|
||||||
|
" b=SoEhlu1Emm2ASqo8jMhz6FIf2nNHt3ouY4Av/pFFEkQ048RqUFP437ap7RbtL2wh0N3Kkm" + CRLF +
|
||||||
|
" AKF2TcTLZ++1nalq+djU+/aP4KYQd4RWWFBjkxDzvCH4bvB1M5AGp4Qz9ldmdMQBWOvvSp" + CRLF +
|
||||||
|
" DIpJW4XNA/uqLSswtjCYbJsSg9Ywv1o=" + CRLF + emailBase
|
||||||
|
|
||||||
|
var signedBadAlgo = "DKIM-Signature: v=1; a=rsa-shasha; q=dns/txt; c=simple/simple;" + CRLF +
|
||||||
|
" s=test; d=tmail.io; l=5; h=from:date:mime-version:received:received;" + CRLF +
|
||||||
|
" bh=GF+NsyJx/iX1Yab8k4suJkMG7DBO2lGAB9F2SCY4GWk=;" + CRLF +
|
||||||
|
" b=SoEhlu1Emm2ASqo8jMhz6FIf2nNHt3ouY4Av/pFFEkQ048RqUFP437ap7RbtL2wh0N3Kkm" + CRLF +
|
||||||
|
" AKF2TcTLZ++1nalq+djU+/aP4KYQd4RWWFBjkxDzvCH4bvB1M5AGp4Qz9ldmdMQBWOvvSp" + CRLF +
|
||||||
|
" DIpJW4XNA/uqLSswtjCYbJsSg9Ywv1o=" + CRLF + emailBase
|
||||||
|
|
||||||
|
var signedDouble = "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 +
|
||||||
|
" bh=GF+NsyJx/iX1Yab8k4suJkMG7DBO2lGAB9F2SCY4GWk=;" + CRLF +
|
||||||
|
" b=SoEhlu1Emm2ASqo8jMhz6FIf2nNHt3ouY4Av/pFFEkQ048RqUFP437ap7RbtL2wh0N3Kkm" + CRLF +
|
||||||
|
" AKF2TcTLZ++1nalq+djU+/aP4KYQd4RWWFBjkxDzvCH4bvB1M5AGp4Qz9ldmdMQBWOvvSp" + CRLF +
|
||||||
|
" DIpJW4XNA/uqLSswtjCYbJsSg9Ywv1o=" + CRLF +
|
||||||
|
"DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed;" + CRLF +
|
||||||
|
" s=test; d=tmail.io; l=5; h=from:date:mime-version:received:received;" + CRLF +
|
||||||
|
" bh=GF+NsyJx/iX1Yab8k4suJkMG7DBO2lGAB9F2SCY4GWk=;" + CRLF +
|
||||||
|
" b=byhiFWd0lAM1sqD1tl8S1DZtKNqgiEZp8jrGds6RRydnZkdX9rCPeL0Q5MYWBQ/JmQrml5" + CRLF +
|
||||||
|
" pIghLwl/EshDBmNy65O6qO8pSSGgZmM3T7SRLMloex8bnrBJ4KSYcHV46639gVEWcBOKW0" + CRLF +
|
||||||
|
" h1djZu2jaTuxGeJzlFVtw3Arf2B93cc=" + CRLF + emailBase
|
||||||
|
|
||||||
func Test_NewSigOptions(t *testing.T) {
|
func Test_NewSigOptions(t *testing.T) {
|
||||||
options := NewSigOptions()
|
options := NewSigOptions()
|
||||||
assert.Equal(t, "rsa-sha256", options.Algo)
|
assert.Equal(t, "rsa-sha256", options.Algo)
|
||||||
assert.Equal(t, "relaxed/simple", options.Canonicalization)
|
assert.Equal(t, "simple/simple", options.Canonicalization)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*func Test_SignConfig(t *testing.T) {
|
/*func Test_SignConfig(t *testing.T) {
|
||||||
@@ -203,3 +254,32 @@ func Test_Sign(t *testing.T) {
|
|||||||
assert.Equal(t, []byte(signedSimpleSimple), emailSimple)
|
assert.Equal(t, []byte(signedSimpleSimple), emailSimple)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
// No From
|
||||||
|
email = []byte(signedNoFrom)
|
||||||
|
status, msg, err = Verify(&email)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "SUCCESS", status)
|
||||||
|
|
||||||
|
// missing mandatory 'a' flag
|
||||||
|
email = []byte(signedMissingFlag)
|
||||||
|
status, msg, err = Verify(&email)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Equal(t, "PERMFAIL", status)
|
||||||
|
assert.Equal(t, "missing 'a' flag in DKIM header", msg)
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
|||||||
11
errors.go
11
errors.go
@@ -34,4 +34,15 @@ var (
|
|||||||
|
|
||||||
// ErrBadDKimTagLBodyTooShort
|
// ErrBadDKimTagLBodyTooShort
|
||||||
ErrBadDKimTagLBodyTooShort = errors.New("bad tag l or bodyLength option. Body length < l value")
|
ErrBadDKimTagLBodyTooShort = errors.New("bad tag l or bodyLength option. Body length < l value")
|
||||||
|
|
||||||
|
// ErrDkimHeaderNotFound when there's no DKIM-Signature header in an email we have to verify
|
||||||
|
ErrDkimHeaderNotFound = errors.New("no DKIM-Signature header field found ")
|
||||||
|
|
||||||
|
// 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")
|
||||||
|
|
||||||
|
// Version not supported
|
||||||
|
ErrDkimVersionUnsuported = errors.New("unsuported DKIM version")
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user