new signature fro sign

This commit is contained in:
Stéphane Depierrepont aka Toorop
2015-05-11 07:54:13 +02:00
parent a96bf59256
commit f9b2a6291f
3 changed files with 65 additions and 82 deletions

59
dkim.go
View File

@@ -11,8 +11,9 @@ import (
"crypto/x509" "crypto/x509"
"encoding/base64" "encoding/base64"
"encoding/pem" "encoding/pem"
//"fmt"
"hash" "hash"
"io/ioutil" //"io/ioutil"
"regexp" "regexp"
"strings" "strings"
) )
@@ -74,61 +75,60 @@ type sigOptions struct {
func NewSigOptions() sigOptions { func NewSigOptions() sigOptions {
return sigOptions{ return sigOptions{
Version: 1, Version: 1,
Canonicalization: "simple/simple", Canonicalization: "relaxed/simple",
Algo: "rsa-sha256", Algo: "rsa-sha256",
Headers: []string{"from"}, Headers: []string{"from"},
BodyLength: 0, BodyLength: 0,
QueryMethods: []string{"dns/txt"}, QueryMethods: []string{"dns/txt"},
AddSignatureTimestamp: false, AddSignatureTimestamp: true,
SignatureExpireIn: 0, SignatureExpireIn: 0,
} }
} }
// Sign signs an email // Sign signs an email
func Sign(email *bytes.Reader, options sigOptions) (*bytes.Reader, error) { func Sign(email *[]byte, options sigOptions) error {
var privateKey *rsa.PrivateKey var privateKey *rsa.PrivateKey
// PrivateKey (required & TODO: valid) // PrivateKey
if options.PrivateKey == "" { if options.PrivateKey == "" {
return nil, ErrSignPrivateKeyRequired return ErrSignPrivateKeyRequired
} }
d, _ := pem.Decode([]byte(options.PrivateKey)) d, _ := pem.Decode([]byte(options.PrivateKey))
key, err := x509.ParsePKCS1PrivateKey(d.Bytes) key, err := x509.ParsePKCS1PrivateKey(d.Bytes)
if err != nil { if err != nil {
return nil, ErrCandNotParsePrivateKey return ErrCandNotParsePrivateKey
} }
privateKey = key privateKey = key
// Domain required // Domain required
if options.Domain == "" { if options.Domain == "" {
return nil, ErrSignDomainRequired return ErrSignDomainRequired
} }
// Selector required // Selector required
if options.Selector == "" { if options.Selector == "" {
return nil, ErrSignSelectorRequired return ErrSignSelectorRequired
} }
// Canonicalization // Canonicalization
options.Canonicalization = strings.ToLower(options.Canonicalization) options.Canonicalization = strings.ToLower(options.Canonicalization)
p := strings.Split(options.Canonicalization, "/") p := strings.Split(options.Canonicalization, "/")
if len(p) > 2 { if len(p) > 2 {
return nil, ErrSignBadCanonicalization return ErrSignBadCanonicalization
} }
if len(p) == 1 { if len(p) == 1 {
options.Canonicalization = options.Canonicalization + "/simple" options.Canonicalization = options.Canonicalization + "/simple"
} }
for _, c := range p { for _, c := range p {
if c != "simple" && c != "relaxed" { if c != "simple" && c != "relaxed" {
return nil, ErrSignBadCanonicalization return ErrSignBadCanonicalization
} }
} }
// Algo // Algo
options.Algo = strings.ToLower(options.Algo) options.Algo = strings.ToLower(options.Algo)
if options.Algo != "rsa-sha1" && options.Algo != "rsa-sha256" { if options.Algo != "rsa-sha1" && options.Algo != "rsa-sha256" {
return nil, ErrSignBadAlgo return ErrSignBadAlgo
} }
// Header must contain "from" // Header must contain "from"
@@ -142,13 +142,13 @@ func Sign(email *bytes.Reader, options sigOptions) (*bytes.Reader, error) {
} }
} }
if !hasFrom { if !hasFrom {
return nil, ErrSignHeaderShouldContainsFrom return ErrSignHeaderShouldContainsFrom
} }
// Normalize // Normalize
headers, body, err := canonicalize(email, options) headers, body, err := canonicalize(email, options)
if err != nil { if err != nil {
return nil, err return err
} }
// hash body // hash body
@@ -169,7 +169,7 @@ func Sign(email *bytes.Reader, options sigOptions) (*bytes.Reader, error) {
// if l tag (body length) // if l tag (body length)
if options.BodyLength != 0 { if options.BodyLength != 0 {
if uint(len(body)) < options.BodyLength { if uint(len(body)) < options.BodyLength {
return nil, ErrBadDKimTagLBodyTooShort return ErrBadDKimTagLBodyTooShort
} }
body = body[0:options.BodyLength] body = body[0:options.BodyLength]
} }
@@ -184,7 +184,7 @@ func Sign(email *bytes.Reader, options sigOptions) (*bytes.Reader, error) {
canonicalizations := strings.Split(options.Canonicalization, "/") canonicalizations := strings.Split(options.Canonicalization, "/")
dHeaderCanonicalized, err := canonicalizeHeader(dHeader, canonicalizations[0]) dHeaderCanonicalized, err := canonicalizeHeader(dHeader, canonicalizations[0])
if err != nil { if err != nil {
return nil, err return err
} }
headers = append(headers, []byte(dHeaderCanonicalized)...) headers = append(headers, []byte(dHeaderCanonicalized)...)
headers = bytes.TrimRight(headers, " \r\n") headers = bytes.TrimRight(headers, " \r\n")
@@ -193,7 +193,7 @@ func Sign(email *bytes.Reader, options sigOptions) (*bytes.Reader, error) {
h2.Write(headers) h2.Write(headers)
sig, err := rsa.SignPKCS1v15(rand.Reader, privateKey, h3, h2.Sum(nil)) sig, err := rsa.SignPKCS1v15(rand.Reader, privateKey, h3, h2.Sum(nil))
if err != nil { if err != nil {
return nil, err return err
} }
sig64 := base64.StdEncoding.EncodeToString(sig) sig64 := base64.StdEncoding.EncodeToString(sig)
@@ -210,32 +210,17 @@ func Sign(email *bytes.Reader, options sigOptions) (*bytes.Reader, error) {
} }
} }
dHeader += subh + CRLF dHeader += subh + CRLF
*email = append([]byte(dHeader), *email...)
// Out return nil
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 // canonicalize returns canonicalized version of header and body
func canonicalize(emailReader *bytes.Reader, options sigOptions) (headers, body []byte, err error) { func canonicalize(email *[]byte, options sigOptions) (headers, body []byte, err error) {
var email []byte
body = []byte{} body = []byte{}
rxReduceWS := regexp.MustCompile(`[ \t]+`) rxReduceWS := regexp.MustCompile(`[ \t]+`)
email, err = ioutil.ReadAll(emailReader)
emailReader.Seek(0, 0)
if err != nil {
return
}
// TODO: \n -> \r\n // TODO: \n -> \r\n
parts := bytes.SplitN(email, []byte{13, 10, 13, 10}, 2) parts := bytes.SplitN(*email, []byte{13, 10, 13, 10}, 2)
if len(parts) != 2 { if len(parts) != 2 {
return headers, body, ErrBadMailFormat return headers, body, ErrBadMailFormat

View File

@@ -1,8 +1,7 @@
package dkim package dkim
import ( import (
"bytes" //"fmt"
"io/ioutil"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@@ -35,7 +34,7 @@ kS5vLkzRI84eiJrm3+IieUqIIicsO+WYxQs+JgVx5XhpPjX4SQjHtwEC2xKkWnEv
selector = "test" selector = "test"
) )
var email = "Received: (qmail 28277 invoked from network); 1 May 2015 09:43:37 -0000" + CRLF + var emailBase = "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: (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 + "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 + " by mo51.mail-out.ovh.net (Postfix) with SMTP id A6E22FF8934" + CRLF +
@@ -86,122 +85,121 @@ var signedRelaxedRelaxed = "DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=rela
" bh=GF+NsyJx/iX1Yab8k4suJkMG7DBO2lGAB9F2SCY4GWk=;" + CRLF + " bh=GF+NsyJx/iX1Yab8k4suJkMG7DBO2lGAB9F2SCY4GWk=;" + CRLF +
" b=byhiFWd0lAM1sqD1tl8S1DZtKNqgiEZp8jrGds6RRydnZkdX9rCPeL0Q5MYWBQ/JmQrml5" + CRLF + " b=byhiFWd0lAM1sqD1tl8S1DZtKNqgiEZp8jrGds6RRydnZkdX9rCPeL0Q5MYWBQ/JmQrml5" + CRLF +
" pIghLwl/EshDBmNy65O6qO8pSSGgZmM3T7SRLMloex8bnrBJ4KSYcHV46639gVEWcBOKW0" + CRLF + " pIghLwl/EshDBmNy65O6qO8pSSGgZmM3T7SRLMloex8bnrBJ4KSYcHV46639gVEWcBOKW0" + CRLF +
" h1djZu2jaTuxGeJzlFVtw3Arf2B93cc=" + CRLF + email " h1djZu2jaTuxGeJzlFVtw3Arf2B93cc=" + CRLF + emailBase
var signedSimpleSimple = "DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=simple/simple;" + CRLF + var signedSimpleSimple = "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; l=5; h=from:date:mime-version:received:received;" + CRLF +
" bh=GF+NsyJx/iX1Yab8k4suJkMG7DBO2lGAB9F2SCY4GWk=;" + CRLF + " bh=GF+NsyJx/iX1Yab8k4suJkMG7DBO2lGAB9F2SCY4GWk=;" + CRLF +
" b=SoEhlu1Emm2ASqo8jMhz6FIf2nNHt3ouY4Av/pFFEkQ048RqUFP437ap7RbtL2wh0N3Kkm" + CRLF + " b=SoEhlu1Emm2ASqo8jMhz6FIf2nNHt3ouY4Av/pFFEkQ048RqUFP437ap7RbtL2wh0N3Kkm" + CRLF +
" AKF2TcTLZ++1nalq+djU+/aP4KYQd4RWWFBjkxDzvCH4bvB1M5AGp4Qz9ldmdMQBWOvvSp" + CRLF + " AKF2TcTLZ++1nalq+djU+/aP4KYQd4RWWFBjkxDzvCH4bvB1M5AGp4Qz9ldmdMQBWOvvSp" + CRLF +
" DIpJW4XNA/uqLSswtjCYbJsSg9Ywv1o=" + CRLF + email " DIpJW4XNA/uqLSswtjCYbJsSg9Ywv1o=" + 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, "simple/simple", options.Canonicalization) assert.Equal(t, "relaxed/simple", options.Canonicalization)
} }
func Test_SignConfig(t *testing.T) { /*func Test_SignConfig(t *testing.T) {
emailReader := bytes.NewReader([]byte(email)) email := []byte(emailBase)
emailToTest := append([]byte(nil), email...)
options := NewSigOptions() options := NewSigOptions()
_, err := Sign(emailReader, options) err := Sign(&emailToTest, options)
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 = privKey options.PrivateKey = privKey
_, err = Sign(emailReader, options) emailToTest = append([]byte(nil), email...)
emailReader.Seek(0, 0) err = Sign(&emailToTest, options)
// 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) emailToTest = append([]byte(nil), email...)
emailReader.Seek(0, 0) err = Sign(&emailToTest, options)
// Selector // Selector
assert.Error(t, err, ErrSignSelectorRequired.Error()) assert.Error(t, err, ErrSignSelectorRequired.Error())
options.Selector = "default" options.Selector = "default"
_, err = Sign(emailReader, options) emailToTest = append([]byte(nil), email...)
err = Sign(&emailToTest, 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) emailToTest = append([]byte(nil), email...)
err = Sign(&emailToTest, 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) emailToTest = append([]byte(nil), email...)
emailReader.Seek(0, 0) err = Sign(&emailToTest, options)
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) emailToTest = append([]byte(nil), email...)
err = Sign(&emailToTest, 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) emailToTest = append([]byte(nil), email...)
err = Sign(&emailToTest, 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) emailToTest = append([]byte(nil), email...)
err = Sign(&emailToTest, 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) emailToTest = append([]byte(nil), email...)
err = Sign(&emailToTest, 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) {
emailReader := bytes.NewReader([]byte(email)) email := []byte(emailBase)
emailToTest := append([]byte(nil), email...)
options := NewSigOptions() options := NewSigOptions()
options.Headers = []string{"from", "date", "mime-version", "received", "received", "In-Reply-To"} options.Headers = []string{"from", "date", "mime-version", "received", "received", "In-Reply-To"}
// simple/simple // simple/simple
options.Canonicalization = "simple/simple" options.Canonicalization = "simple/simple"
header, body, err := canonicalize(emailReader, options) header, body, err := canonicalize(&emailToTest, options)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, []byte(headerSimple), header) assert.Equal(t, []byte(headerSimple), header)
assert.Equal(t, []byte(bodySimple), body) assert.Equal(t, []byte(bodySimple), body)
// relaxed/relaxed // relaxed/relaxed
emailToTest = append([]byte(nil), email...)
options.Canonicalization = "relaxed/relaxed" options.Canonicalization = "relaxed/relaxed"
header, body, err = canonicalize(emailReader, options) header, body, err = canonicalize(&emailToTest, options)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, []byte(headerRelaxed), header) assert.Equal(t, []byte(headerRelaxed), header)
assert.Equal(t, []byte(bodyRelaxed), body) assert.Equal(t, []byte(bodyRelaxed), body)
} }
*/
func Test_Sign(t *testing.T) { func Test_Sign(t *testing.T) {
emailReader := bytes.NewReader([]byte(email)) email := []byte(emailBase)
emailRelaxed := append([]byte(nil), email...)
options := NewSigOptions() options := NewSigOptions()
options.PrivateKey = privKey options.PrivateKey = privKey
options.Domain = domain options.Domain = domain
options.Selector = selector options.Selector = selector
//options.AddSignatureTimestamp = true
//options.SignatureExpireIn = 3600 //options.SignatureExpireIn = 3600
options.BodyLength = 5 options.BodyLength = 5
options.Headers = []string{"from", "date", "mime-version", "received", "received"} options.Headers = []string{"from", "date", "mime-version", "received", "received"}
options.AddSignatureTimestamp = false
options.Canonicalization = "relaxed/relaxed" options.Canonicalization = "relaxed/relaxed"
signedEmailReader, err := Sign(emailReader, options) err := Sign(&emailRelaxed, options)
assert.NoError(t, err) assert.NoError(t, err)
raw, _ := ioutil.ReadAll(signedEmailReader) assert.Equal(t, []byte(signedRelaxedRelaxed), emailRelaxed)
emailReader.Seek(0, 0)
assert.Equal(t, []byte(signedRelaxedRelaxed), raw)
options.Canonicalization = "simple/simple" options.Canonicalization = "simple/simple"
emailReader, err = Sign(emailReader, options) emailSimple := append([]byte(nil), email...)
assert.NoError(t, err) err = Sign(&emailSimple, options)
raw, _ = ioutil.ReadAll(emailReader) assert.Equal(t, []byte(signedSimpleSimple), emailSimple)
assert.Equal(t, []byte(signedSimpleSimple), raw)
//raw, _ = ioutil.ReadAll(emailReader)
//fmt.Println(string(raw))
} }

2
watch
View File

@@ -1,4 +1,4 @@
while true while true
do do
inotifywait -q -r -e modify,attrib,close_write,move,create,delete . && go test -v inotifywait -q -r -e modify,attrib,close_write,move,create,delete . && echo "--------------" && go test -v
done done