Sign: check & sanitize config

This commit is contained in:
Stéphane Depierrepont aka Toorop
2015-05-03 16:03:02 +02:00
parent a975eb7186
commit e6f591e5e3
3 changed files with 145 additions and 21 deletions

106
dkim.go
View File

@@ -2,38 +2,118 @@ package dkim
import (
"bytes"
"strings"
"time"
)
// sigOptions represents signing options
type sigOptions struct {
PrivateKey string
Domain string
Selector string
Auid string
// DKIM version (default 1)
Version uint
// Private key used for signing (required)
PrivateKey string
// Domain (required)
Domain string
// Selector (required)
Selector string
// The Agent of User IDentifier
Auid string
// Message canonicalization (plain-text; OPTIONAL, default is
// "simple/simple"). This tag informs the Verifier of the type of
// canonicalization used to prepare the message for signing.
Canonicalization string
Algo string
Headers []string
Timestamp time.Time
Expiration time.Time
// The algorithm used to generate the signature
//"rsa-sha1" or "rsa-sha256"
Algo string
// Signed header fields
Headers []string
// Body length count( if set to 0 this tag is ommited in Dkim header)
BodyLength uint
// Query Methods used to retrieve the public key
QueryMethods []string
// Add a signature timestamp
AddSignatureTimestamp bool
// Time validity of the signature (0=never)
SignatureExpireIn time.Duration
}
// NewSigOption returns new sigoption with some defaults value
func NewSigOptions() sigOptions {
return sigOptions{
Canonicalization: "simple/simple",
Algo: "rsa-sha256",
Version: 1,
Canonicalization: "simple/simple",
Algo: "rsa-sha256",
Headers: []string{"from"},
BodyLength: 0,
QueryMethods: []string{"dns/txt"},
AddSignatureTimestamp: false,
SignatureExpireIn: 0 * time.Second,
}
}
// Sign signs an email
func Sign(email *bytes.Reader, options sigOptions) (*bytes.Reader, error) {
// check config
// check && sanitize config
// private key (not empty & TODO: valid)
// PrivateKey (required & TODO: valid)
if options.PrivateKey == "" {
return nil, ErrConfigNoPrivateKey
return nil, ErrSignPrivateKeyRequired
}
// Domain required
if options.Domain == "" {
return nil, ErrSignDomainRequired
}
// Selector required
if options.Selector == "" {
return nil, ErrSignSelectorRequired
}
// Canonicalization
options.Canonicalization = strings.ToLower(options.Canonicalization)
p := strings.Split(options.Canonicalization, "/")
if len(p) > 2 {
return nil, ErrSignBadCanonicalization
}
for _, c := range p {
if c != "simple" && c != "relaxed" {
return nil, ErrSignBadCanonicalization
}
}
// Algo
options.Algo = strings.ToLower(options.Algo)
if options.Algo != "rsa-sha1" && options.Algo != "rsa-sha256" {
return nil, ErrSignBadAlgo
}
// Header must contain "from"
// normalize -> strtlower
hasFrom := false
for i, h := range options.Headers {
options.Headers[i] = strings.ToLower(h)
if h == "from" {
hasFrom = true
}
}
if !hasFrom {
return nil, ErrSignHeaderShouldContainsFrom
}
//
return nil, nil
}

View File

@@ -27,16 +27,47 @@ func Test_NewSigOptions(t *testing.T) {
assert.Equal(t, "simple/simple", options.Canonicalization)
}
func Test_Sign(t *testing.T) {
func Test_SignConfig(t *testing.T) {
emailReader := bytes.NewReader([]byte(email))
options := NewSigOptions()
_, err := Sign(emailReader, options)
assert.NotNil(t, err)
// && err No private key
assert.Error(t, ErrConfigNoPrivateKey)
assert.EqualError(t, err, ErrSignPrivateKeyRequired.Error())
options.PrivateKey = "toto"
_, err = Sign(emailReader, options)
// No
assert.Error(t, ErrConfigNoDomain)
// Domain
assert.EqualError(t, err, ErrSignDomainRequired.Error())
options.Domain = "toorop.fr"
_, err = Sign(emailReader, options)
// Selector
assert.Error(t, err, ErrSignSelectorRequired.Error())
options.Selector = "default"
_, err = Sign(emailReader, options)
assert.NoError(t, err)
// Canonicalization
options.Canonicalization = "simple/relaxed/simple"
_, err = Sign(emailReader, options)
assert.EqualError(t, err, ErrSignBadCanonicalization.Error())
options.Canonicalization = "simple/relax"
_, err = Sign(emailReader, options)
assert.EqualError(t, err, ErrSignBadCanonicalization.Error())
options.Canonicalization = "relaxed"
_, err = Sign(emailReader, options)
assert.NoError(t, err)
options.Canonicalization = "SiMple/relAxed"
_, err = Sign(emailReader, options)
assert.NoError(t, err)
// header
/*options.Headers = []string{"toto"}
_, err = Sign(emailReader, options)
assert.EqualError(t, err, ErrSignHeaderShouldContainsFrom.Error())*/
}

View File

@@ -5,8 +5,21 @@ import (
)
var (
// ErrConfigNoPrivateKey when there not private key in config
ErrConfigNoPrivateKey = errors.New("private key not defined in config")
// ErrConfigNoDomain when there is no domain defined in config
ErrConfigNoDomain = errors.New("domain not defined in config")
// ErrConfigPrivateKeyRequired when there not private key in config
ErrSignPrivateKeyRequired = errors.New("PrivateKey is required in config")
// ErrSignDomainRequired when there is no domain defined in config
ErrSignDomainRequired = errors.New("Domain is required in config")
// ErrSignSelectorRequired when there is no Selcteir defined in config
ErrSignSelectorRequired = errors.New("Selector is required in config")
// If Headers is specified it should at least contain 'from'
ErrSignHeaderShouldContainsFrom = errors.New("Header must contains 'from' field")
// If bad Canonicalization parameter
ErrSignBadCanonicalization = errors.New("bad Canonicalization parameter")
// Bad algorithm
ErrSignBadAlgo = errors.New("bar algorithm. Only rsa-sha1 or rsa-sha256 are permitted")
)