sign implemted (need real life testing)
This commit is contained in:
193
dkim.go
193
dkim.go
@@ -3,6 +3,15 @@ package dkim
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"container/list"
|
"container/list"
|
||||||
|
"crypto"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/sha1"
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/pem"
|
||||||
|
"hash"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -11,6 +20,9 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
CRLF = "\r\n"
|
CRLF = "\r\n"
|
||||||
|
TAB = "\t"
|
||||||
|
FWS = CRLF + TAB
|
||||||
|
MaxHeaderLineLength = 70
|
||||||
)
|
)
|
||||||
|
|
||||||
// sigOptions represents signing options
|
// sigOptions represents signing options
|
||||||
@@ -54,6 +66,9 @@ type sigOptions struct {
|
|||||||
|
|
||||||
// Time validity of the signature (0=never)
|
// Time validity of the signature (0=never)
|
||||||
SignatureExpireIn time.Duration
|
SignatureExpireIn time.Duration
|
||||||
|
|
||||||
|
// CopiedHeaderFileds
|
||||||
|
CopiedHeaderFileds []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSigOption returns new sigoption with some defaults value
|
// NewSigOption returns new sigoption with some defaults value
|
||||||
@@ -72,6 +87,7 @@ func NewSigOptions() sigOptions {
|
|||||||
|
|
||||||
// Sign signs an email
|
// Sign signs an email
|
||||||
func Sign(email *bytes.Reader, options sigOptions) (*bytes.Reader, error) {
|
func Sign(email *bytes.Reader, options sigOptions) (*bytes.Reader, error) {
|
||||||
|
var privateKey *rsa.PrivateKey
|
||||||
// check && sanitize config
|
// check && sanitize config
|
||||||
|
|
||||||
// PrivateKey (required & TODO: valid)
|
// PrivateKey (required & TODO: valid)
|
||||||
@@ -79,6 +95,13 @@ func Sign(email *bytes.Reader, options sigOptions) (*bytes.Reader, error) {
|
|||||||
return nil, ErrSignPrivateKeyRequired
|
return nil, ErrSignPrivateKeyRequired
|
||||||
}
|
}
|
||||||
|
|
||||||
|
d, _ := pem.Decode([]byte(options.PrivateKey))
|
||||||
|
key, err := x509.ParsePKCS1PrivateKey(d.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
privateKey = key
|
||||||
|
|
||||||
// Domain required
|
// Domain required
|
||||||
if options.Domain == "" {
|
if options.Domain == "" {
|
||||||
return nil, ErrSignDomainRequired
|
return nil, ErrSignDomainRequired
|
||||||
@@ -125,13 +148,74 @@ func Sign(email *bytes.Reader, options sigOptions) (*bytes.Reader, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Normalize
|
// Normalize
|
||||||
//normalizedHeaders, NormalizedBody, err := normalize(email, options)
|
headers, body, err := canonicalize(email, options)
|
||||||
|
if err != nil {
|
||||||
canonicalize(email, options)
|
return nil, err
|
||||||
|
|
||||||
return nil, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// hash body
|
||||||
|
var bodyHash string
|
||||||
|
var h1, 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
|
||||||
|
}
|
||||||
|
bodyHash = base64.StdEncoding.EncodeToString(h1.Sum(body))
|
||||||
|
|
||||||
|
// Get dkim header base
|
||||||
|
dkimHeader := NewDkimHeaderBySigOptions(options)
|
||||||
|
dHeader := dkimHeader.GetHeaderBase(bodyHash)
|
||||||
|
|
||||||
|
canonicalizations := strings.Split(options.Canonicalization, "/")
|
||||||
|
dHeaderCanonicalized, err := canonicalizeHeader(dHeader, canonicalizations[0])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
headers = append(headers, []byte(dHeaderCanonicalized)...)
|
||||||
|
|
||||||
|
// sign
|
||||||
|
h2.Write(headers)
|
||||||
|
sig, err := rsa.SignPKCS1v15(rand.Reader, privateKey, h3, h2.Sum(nil))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sig64 := base64.StdEncoding.EncodeToString(sig)
|
||||||
|
|
||||||
|
// add to DKIM-Header
|
||||||
|
|
||||||
|
dHeader += ";" + FWS
|
||||||
|
subh := "b="
|
||||||
|
l := len(subh)
|
||||||
|
for _, c := range sig64 {
|
||||||
|
subh += string(c)
|
||||||
|
l++
|
||||||
|
if l >= MaxHeaderLineLength {
|
||||||
|
dHeader += subh + FWS
|
||||||
|
subh = ""
|
||||||
|
l = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dHeader += subh + CRLF
|
||||||
|
|
||||||
|
// Out
|
||||||
|
rawmail := []byte(dHeader)
|
||||||
|
t, err := ioutil.ReadAll(email)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rawmail = append(rawmail, t...)
|
||||||
|
return bytes.NewReader(rawmail), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// canonicalize returns canonicalized version of header and body
|
||||||
func canonicalize(emailReader *bytes.Reader, options sigOptions) (headers, body []byte, err error) {
|
func canonicalize(emailReader *bytes.Reader, options sigOptions) (headers, body []byte, err error) {
|
||||||
var email []byte
|
var email []byte
|
||||||
body = []byte{}
|
body = []byte{}
|
||||||
@@ -157,8 +241,6 @@ func canonicalize(emailReader *bytes.Reader, options sigOptions) (headers, body
|
|||||||
canonicalizations := strings.Split(options.Canonicalization, "/")
|
canonicalizations := strings.Split(options.Canonicalization, "/")
|
||||||
|
|
||||||
// canonicalyze header
|
// canonicalyze header
|
||||||
//var headersMap [][]byte
|
|
||||||
//headersMap := [][]byte{}
|
|
||||||
headersList := list.New()
|
headersList := list.New()
|
||||||
currentHeader := []byte{}
|
currentHeader := []byte{}
|
||||||
for _, line := range bytes.SplitAfter(parts[0], []byte{10}) {
|
for _, line := range bytes.SplitAfter(parts[0], []byte{10}) {
|
||||||
@@ -170,7 +252,6 @@ func canonicalize(emailReader *bytes.Reader, options sigOptions) (headers, body
|
|||||||
} else {
|
} else {
|
||||||
// New header, save current if exists
|
// New header, save current if exists
|
||||||
if len(currentHeader) != 0 {
|
if len(currentHeader) != 0 {
|
||||||
//headersMap = append(headersMap, currentHeader)
|
|
||||||
headersList.PushBack(string(currentHeader))
|
headersList.PushBack(string(currentHeader))
|
||||||
currentHeader = []byte{}
|
currentHeader = []byte{}
|
||||||
|
|
||||||
@@ -201,51 +282,13 @@ func canonicalize(emailReader *bytes.Reader, options sigOptions) (headers, body
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if canonicalizations[0] == "simple" {
|
//if canonicalizations[0] == "simple" {
|
||||||
// The "simple" header canonicalization algorithm does not change header
|
|
||||||
// fields in any way. Header fields MUST be presented to the signing or
|
|
||||||
// verification algorithm exactly as they are in the message being
|
|
||||||
// signed or verified. In particular, header field names MUST NOT be
|
|
||||||
// case folded and whitespace MUST NOT be changed.
|
|
||||||
for e := headersToKeepList.Front(); e != nil; e = e.Next() {
|
for e := headersToKeepList.Front(); e != nil; e = e.Next() {
|
||||||
headers = append(headers, []byte(e.Value.(string))...)
|
cHeader, err := canonicalizeHeader(e.Value.(string), canonicalizations[0])
|
||||||
}
|
if err != nil {
|
||||||
} else {
|
return headers, body, err
|
||||||
// The "relaxed" header canonicalization algorithm MUST apply the
|
|
||||||
// following steps in order:
|
|
||||||
|
|
||||||
// Convert all header field names (not the header field values) to
|
|
||||||
// lowercase. For example, convert "SUBJect: AbC" to "subject: AbC".
|
|
||||||
|
|
||||||
// Unfold all header field continuation lines as described in
|
|
||||||
// [RFC5322]; in particular, lines with terminators embedded in
|
|
||||||
// continued header field values (that is, CRLF sequences followed by
|
|
||||||
// WSP) MUST be interpreted without the CRLF. Implementations MUST
|
|
||||||
// NOT remove the CRLF at the end of the header field value.
|
|
||||||
|
|
||||||
// Convert all sequences of one or more WSP characters to a single SP
|
|
||||||
// character. WSP characters here include those before and after a
|
|
||||||
// line folding boundary.
|
|
||||||
|
|
||||||
// Delete all WSP characters at the end of each unfolded header field
|
|
||||||
// value.
|
|
||||||
|
|
||||||
// Delete any WSP characters remaining before and after the colon
|
|
||||||
// separating the header field name from the header field value. The
|
|
||||||
// colon separator MUST be retained.
|
|
||||||
for e := headersToKeepList.Front(); e != nil; e = e.Next() {
|
|
||||||
kv := strings.SplitN(e.Value.(string), ":", 2)
|
|
||||||
if len(kv) != 2 {
|
|
||||||
return []byte{}, []byte{}, ErrBadMailFormatHeaders
|
|
||||||
}
|
|
||||||
k := strings.ToLower(kv[0])
|
|
||||||
k = strings.TrimSpace(k)
|
|
||||||
v := strings.Replace(kv[1], "\n", "", -1)
|
|
||||||
v = strings.Replace(v, "\r", "", -1)
|
|
||||||
v = rxReduceWS.ReplaceAllString(v, " ")
|
|
||||||
v = strings.TrimSpace(v)
|
|
||||||
headers = append(headers, []byte(k+":"+v+CRLF)...)
|
|
||||||
}
|
}
|
||||||
|
headers = append(headers, []byte(cHeader)...)
|
||||||
}
|
}
|
||||||
// canonicalyze body
|
// canonicalyze body
|
||||||
if canonicalizations[1] == "simple" {
|
if canonicalizations[1] == "simple" {
|
||||||
@@ -290,3 +333,51 @@ func canonicalize(emailReader *bytes.Reader, options sigOptions) (headers, body
|
|||||||
println(string(body))*/
|
println(string(body))*/
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// canonicalizeHeader returns canonicalized version of header
|
||||||
|
func canonicalizeHeader(header string, algo string) (string, error) {
|
||||||
|
rxReduceWS := regexp.MustCompile(`[ \t]+`)
|
||||||
|
if algo == "simple" {
|
||||||
|
// The "simple" header canonicalization algorithm does not change header
|
||||||
|
// fields in any way. Header fields MUST be presented to the signing or
|
||||||
|
// verification algorithm exactly as they are in the message being
|
||||||
|
// signed or verified. In particular, header field names MUST NOT be
|
||||||
|
// case folded and whitespace MUST NOT be changed.
|
||||||
|
return header, nil
|
||||||
|
} else if algo == "relaxed" {
|
||||||
|
// The "relaxed" header canonicalization algorithm MUST apply the
|
||||||
|
// following steps in order:
|
||||||
|
|
||||||
|
// Convert all header field names (not the header field values) to
|
||||||
|
// lowercase. For example, convert "SUBJect: AbC" to "subject: AbC".
|
||||||
|
|
||||||
|
// Unfold all header field continuation lines as described in
|
||||||
|
// [RFC5322]; in particular, lines with terminators embedded in
|
||||||
|
// continued header field values (that is, CRLF sequences followed by
|
||||||
|
// WSP) MUST be interpreted without the CRLF. Implementations MUST
|
||||||
|
// NOT remove the CRLF at the end of the header field value.
|
||||||
|
|
||||||
|
// Convert all sequences of one or more WSP characters to a single SP
|
||||||
|
// character. WSP characters here include those before and after a
|
||||||
|
// line folding boundary.
|
||||||
|
|
||||||
|
// Delete all WSP characters at the end of each unfolded header field
|
||||||
|
// value.
|
||||||
|
|
||||||
|
// Delete any WSP characters remaining before and after the colon
|
||||||
|
// separating the header field name from the header field value. The
|
||||||
|
// colon separator MUST be retained.
|
||||||
|
kv := strings.SplitN(header, ":", 2)
|
||||||
|
if len(kv) != 2 {
|
||||||
|
return header, ErrBadMailFormatHeaders
|
||||||
|
}
|
||||||
|
k := strings.ToLower(kv[0])
|
||||||
|
k = strings.TrimSpace(k)
|
||||||
|
v := strings.Replace(kv[1], "\n", "", -1)
|
||||||
|
v = strings.Replace(v, "\r", "", -1)
|
||||||
|
v = rxReduceWS.ReplaceAllString(v, " ")
|
||||||
|
v = strings.TrimSpace(v)
|
||||||
|
return k + ":" + v + CRLF, nil
|
||||||
|
}
|
||||||
|
return header, ErrSignBadCanonicalization
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package dkim
|
package dkim
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -180,3 +182,99 @@ type DkimHeader struct {
|
|||||||
// tag z
|
// tag z
|
||||||
CopiedHeaderFileds []string
|
CopiedHeaderFileds []string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewDkimHeaderBySigOptions return a new DkimHeader initioalized with sigOptions value
|
||||||
|
func NewDkimHeaderBySigOptions(options sigOptions) *DkimHeader {
|
||||||
|
h := new(DkimHeader)
|
||||||
|
h.Version = "1"
|
||||||
|
h.Algorithm = options.Algo
|
||||||
|
h.MessageCanonicalization = options.Canonicalization
|
||||||
|
h.Domain = options.Domain
|
||||||
|
h.Headers = options.Headers
|
||||||
|
h.Auid = options.Auid
|
||||||
|
h.BodyLength = options.BodyLength
|
||||||
|
h.QueryMethods = options.QueryMethods
|
||||||
|
h.Selector = options.Selector
|
||||||
|
if options.AddSignatureTimestamp {
|
||||||
|
h.SignatureTimestamp = time.Now()
|
||||||
|
}
|
||||||
|
if options.SignatureExpireIn.Seconds() > 0 {
|
||||||
|
h.SignatureExpiration = time.Now().Add(options.SignatureExpireIn)
|
||||||
|
}
|
||||||
|
h.CopiedHeaderFileds = options.CopiedHeaderFileds
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetHeaderBase return base header for signers
|
||||||
|
// Todo: some refactoring...
|
||||||
|
func (d *DkimHeader) GetHeaderBase(bodyHash string) string {
|
||||||
|
h := "DKIM-Signature: v=" + d.Version + "; a=" + d.Algorithm + "; q=" + strings.Join(d.QueryMethods, ":") + "; c=" + d.MessageCanonicalization + ";" + CRLF + TAB
|
||||||
|
subh := "s=" + d.Selector + ";"
|
||||||
|
if len(subh)+len(d.Domain)+4 > MaxHeaderLineLength {
|
||||||
|
h += subh + FWS
|
||||||
|
subh = ""
|
||||||
|
}
|
||||||
|
subh += " d=" + d.Domain + ";"
|
||||||
|
|
||||||
|
// signature timestamp
|
||||||
|
if !d.SignatureTimestamp.IsZero() {
|
||||||
|
ts := d.SignatureTimestamp.Unix()
|
||||||
|
if len(subh)+14 > MaxHeaderLineLength {
|
||||||
|
h += subh + FWS
|
||||||
|
subh = ""
|
||||||
|
}
|
||||||
|
subh += " t=" + fmt.Sprintf("%d", ts) + ";"
|
||||||
|
}
|
||||||
|
if len(subh)+len(d.Domain)+4 > MaxHeaderLineLength {
|
||||||
|
h += subh + FWS
|
||||||
|
subh = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expiration
|
||||||
|
if !d.SignatureExpiration.IsZero() {
|
||||||
|
ts := d.SignatureExpiration.Unix()
|
||||||
|
if len(subh)+14 > MaxHeaderLineLength {
|
||||||
|
h += subh + FWS
|
||||||
|
subh = ""
|
||||||
|
}
|
||||||
|
subh += " x=" + fmt.Sprintf("%d", ts) + ";"
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Headers
|
||||||
|
if len(subh)+len(d.Headers)+4 > MaxHeaderLineLength {
|
||||||
|
h += subh + FWS
|
||||||
|
subh = ""
|
||||||
|
}
|
||||||
|
subh += " h="
|
||||||
|
for _, header := range d.Headers {
|
||||||
|
if len(subh)+len(header)+1 > MaxHeaderLineLength {
|
||||||
|
h += subh + FWS
|
||||||
|
subh = ""
|
||||||
|
}
|
||||||
|
subh += header + ":"
|
||||||
|
}
|
||||||
|
subh = subh[:len(subh)-1] + ";"
|
||||||
|
|
||||||
|
// BodyHash
|
||||||
|
if len(subh)+5+len(bodyHash) > MaxHeaderLineLength {
|
||||||
|
h += subh + FWS
|
||||||
|
subh = ""
|
||||||
|
} else {
|
||||||
|
subh += " "
|
||||||
|
}
|
||||||
|
subh += "bh="
|
||||||
|
l := len(subh)
|
||||||
|
for _, c := range bodyHash {
|
||||||
|
subh += string(c)
|
||||||
|
l++
|
||||||
|
if l >= MaxHeaderLineLength {
|
||||||
|
h += subh + FWS
|
||||||
|
subh = ""
|
||||||
|
l = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
h += subh
|
||||||
|
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|||||||
27
dkim_test.go
27
dkim_test.go
@@ -2,12 +2,15 @@ package dkim
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"fmt"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"io/ioutil"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
privKey = `MIICXQIBAAKBgQDNUXO+Qsl1tw+GjrqFajz0ERSEUs1FHSL/+udZRWn1Atw8gz0+
|
privKey = `-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIICXQIBAAKBgQDNUXO+Qsl1tw+GjrqFajz0ERSEUs1FHSL/+udZRWn1Atw8gz0+
|
||||||
tcGqhWChBDeU9gY5sKLEAZnX3FjC/T/IbqeiSM68kS5vLkzRI84eiJrm3+IieUqI
|
tcGqhWChBDeU9gY5sKLEAZnX3FjC/T/IbqeiSM68kS5vLkzRI84eiJrm3+IieUqI
|
||||||
IicsO+WYxQs+JgVx5XhpPjX4SQjHtwEC2xKkWnEv+VPgO1JWdooURcSC6QIDAQAB
|
IicsO+WYxQs+JgVx5XhpPjX4SQjHtwEC2xKkWnEv+VPgO1JWdooURcSC6QIDAQAB
|
||||||
AoGAM9exRgVPIS4L+Ynohu+AXJBDgfX2ZtEomUIdUGk6i+cg/RaWTFNQh2IOOBn8
|
AoGAM9exRgVPIS4L+Ynohu+AXJBDgfX2ZtEomUIdUGk6i+cg/RaWTFNQh2IOOBn8
|
||||||
@@ -19,7 +22,8 @@ uGhq0DPojmhsmUC8jUeLe79CllZNP3LU1wJBAIZcoCnI7g5Bcdr4nyxfJ4pkw4cQ
|
|||||||
S4FT0XAZPR/YZrADo8/SWCWPdFTGSuaf17nL6vLD1zljK/skY5LwshrvUCMCQQDM
|
S4FT0XAZPR/YZrADo8/SWCWPdFTGSuaf17nL6vLD1zljK/skY5LwshrvUCMCQQDM
|
||||||
MY7ehj6DVFHYlt2LFSyhInCZscTencgK24KfGF5t1JZlwt34YaMqjAMACmi/55Fc
|
MY7ehj6DVFHYlt2LFSyhInCZscTencgK24KfGF5t1JZlwt34YaMqjAMACmi/55Fc
|
||||||
e7DIxW5nI/nDZrOY+EAjAkA3BHUx3PeXkXJnXjlh7nGZmk/v8tB5fiofAwfXNfL7
|
e7DIxW5nI/nDZrOY+EAjAkA3BHUx3PeXkXJnXjlh7nGZmk/v8tB5fiofAwfXNfL7
|
||||||
bz0ZrT2Caz995Dpjommh5aMpCJvUGsrYCG6/Pbha9NXl`
|
bz0ZrT2Caz995Dpjommh5aMpCJvUGsrYCG6/Pbha9NXl
|
||||||
|
-----END RSA PRIVATE KEY-----`
|
||||||
|
|
||||||
pubKey = `MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDNUXO+Qsl1tw+GjrqFajz0ERSE
|
pubKey = `MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDNUXO+Qsl1tw+GjrqFajz0ERSE
|
||||||
Us1FHSL/+udZRWn1Atw8gz0+tcGqhWChBDeU9gY5sKLEAZnX3FjC/T/IbqeiSM68
|
Us1FHSL/+udZRWn1Atw8gz0+tcGqhWChBDeU9gY5sKLEAZnX3FjC/T/IbqeiSM68
|
||||||
@@ -90,45 +94,55 @@ func Test_SignConfig(t *testing.T) {
|
|||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
// && err No private key
|
// && err No private key
|
||||||
assert.EqualError(t, err, ErrSignPrivateKeyRequired.Error())
|
assert.EqualError(t, err, ErrSignPrivateKeyRequired.Error())
|
||||||
options.PrivateKey = "toto"
|
options.PrivateKey = privKey
|
||||||
_, err = Sign(emailReader, options)
|
_, err = Sign(emailReader, options)
|
||||||
|
emailReader.Seek(0, 0)
|
||||||
|
|
||||||
// Domain
|
// Domain
|
||||||
assert.EqualError(t, err, ErrSignDomainRequired.Error())
|
assert.EqualError(t, err, ErrSignDomainRequired.Error())
|
||||||
options.Domain = "toorop.fr"
|
options.Domain = "toorop.fr"
|
||||||
_, err = Sign(emailReader, options)
|
_, err = Sign(emailReader, options)
|
||||||
|
emailReader.Seek(0, 0)
|
||||||
|
|
||||||
// Selector
|
// Selector
|
||||||
assert.Error(t, err, ErrSignSelectorRequired.Error())
|
assert.Error(t, err, ErrSignSelectorRequired.Error())
|
||||||
options.Selector = "default"
|
options.Selector = "default"
|
||||||
_, err = Sign(emailReader, options)
|
_, err = Sign(emailReader, options)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
emailReader.Seek(0, 0)
|
||||||
|
|
||||||
// Canonicalization
|
// Canonicalization
|
||||||
options.Canonicalization = "simple/relaxed/simple"
|
options.Canonicalization = "simple/relaxed/simple"
|
||||||
_, err = Sign(emailReader, options)
|
_, err = Sign(emailReader, options)
|
||||||
assert.EqualError(t, err, ErrSignBadCanonicalization.Error())
|
assert.EqualError(t, err, ErrSignBadCanonicalization.Error())
|
||||||
|
emailReader.Seek(0, 0)
|
||||||
|
|
||||||
options.Canonicalization = "simple/relax"
|
options.Canonicalization = "simple/relax"
|
||||||
_, err = Sign(emailReader, options)
|
_, err = Sign(emailReader, options)
|
||||||
|
emailReader.Seek(0, 0)
|
||||||
assert.EqualError(t, err, ErrSignBadCanonicalization.Error())
|
assert.EqualError(t, err, ErrSignBadCanonicalization.Error())
|
||||||
|
emailReader.Seek(0, 0)
|
||||||
|
|
||||||
options.Canonicalization = "relaxed"
|
options.Canonicalization = "relaxed"
|
||||||
_, err = Sign(emailReader, options)
|
_, err = Sign(emailReader, options)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
emailReader.Seek(0, 0)
|
||||||
|
|
||||||
options.Canonicalization = "SiMple/relAxed"
|
options.Canonicalization = "SiMple/relAxed"
|
||||||
_, err = Sign(emailReader, options)
|
_, err = Sign(emailReader, options)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
emailReader.Seek(0, 0)
|
||||||
|
|
||||||
// header
|
// header
|
||||||
options.Headers = []string{"toto"}
|
options.Headers = []string{"toto"}
|
||||||
_, err = Sign(emailReader, options)
|
_, err = Sign(emailReader, options)
|
||||||
assert.EqualError(t, err, ErrSignHeaderShouldContainsFrom.Error())
|
assert.EqualError(t, err, ErrSignHeaderShouldContainsFrom.Error())
|
||||||
|
emailReader.Seek(0, 0)
|
||||||
|
|
||||||
options.Headers = []string{"To", "From"}
|
options.Headers = []string{"To", "From"}
|
||||||
_, err = Sign(emailReader, options)
|
_, err = Sign(emailReader, options)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
emailReader.Seek(0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_canonicalize(t *testing.T) {
|
func Test_canonicalize(t *testing.T) {
|
||||||
@@ -155,9 +169,14 @@ func Test_Sign(t *testing.T) {
|
|||||||
emailReader := bytes.NewReader([]byte(email))
|
emailReader := bytes.NewReader([]byte(email))
|
||||||
options := NewSigOptions()
|
options := NewSigOptions()
|
||||||
options.PrivateKey = privKey
|
options.PrivateKey = privKey
|
||||||
options.Canonicalization = "simple/relaxed"
|
options.Canonicalization = "relaxed/relaxed"
|
||||||
options.Domain = domain
|
options.Domain = domain
|
||||||
options.Selector = selector
|
options.Selector = selector
|
||||||
|
options.AddSignatureTimestamp = true
|
||||||
|
options.SignatureExpireIn = 3600
|
||||||
|
options.Headers = []string{"from", "date", "mime-version", "received", "received", "In-Reply-To"}
|
||||||
emailReader, err := Sign(emailReader, options)
|
emailReader, err := Sign(emailReader, options)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
raw, _ := ioutil.ReadAll(emailReader)
|
||||||
|
fmt.Println(string(raw))
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user