Sign: check & sanitize config
This commit is contained in:
106
dkim.go
106
dkim.go
@@ -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
|
||||
}
|
||||
|
||||
39
dkim_test.go
39
dkim_test.go
@@ -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())*/
|
||||
|
||||
}
|
||||
|
||||
21
errors.go
21
errors.go
@@ -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")
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user