Sign: check & sanitize config
This commit is contained in:
106
dkim.go
106
dkim.go
@@ -2,38 +2,118 @@ package dkim
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// sigOptions represents signing options
|
// sigOptions represents signing options
|
||||||
type sigOptions struct {
|
type sigOptions struct {
|
||||||
PrivateKey string
|
|
||||||
Domain string
|
// DKIM version (default 1)
|
||||||
Selector string
|
Version uint
|
||||||
Auid string
|
|
||||||
|
// 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
|
Canonicalization string
|
||||||
Algo string
|
|
||||||
Headers []string
|
// The algorithm used to generate the signature
|
||||||
Timestamp time.Time
|
//"rsa-sha1" or "rsa-sha256"
|
||||||
Expiration time.Time
|
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
|
// NewSigOption returns new sigoption with some defaults value
|
||||||
func NewSigOptions() sigOptions {
|
func NewSigOptions() sigOptions {
|
||||||
return sigOptions{
|
return sigOptions{
|
||||||
Canonicalization: "simple/simple",
|
Version: 1,
|
||||||
Algo: "rsa-sha256",
|
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
|
// Sign signs an email
|
||||||
func Sign(email *bytes.Reader, options sigOptions) (*bytes.Reader, error) {
|
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 == "" {
|
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
|
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)
|
assert.Equal(t, "simple/simple", options.Canonicalization)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_Sign(t *testing.T) {
|
func Test_SignConfig(t *testing.T) {
|
||||||
emailReader := bytes.NewReader([]byte(email))
|
emailReader := bytes.NewReader([]byte(email))
|
||||||
options := NewSigOptions()
|
options := NewSigOptions()
|
||||||
_, err := Sign(emailReader, options)
|
_, err := Sign(emailReader, options)
|
||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
// && err No private key
|
// && err No private key
|
||||||
assert.Error(t, ErrConfigNoPrivateKey)
|
assert.EqualError(t, err, ErrSignPrivateKeyRequired.Error())
|
||||||
options.PrivateKey = "toto"
|
options.PrivateKey = "toto"
|
||||||
_, err = Sign(emailReader, options)
|
_, 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 (
|
var (
|
||||||
// ErrConfigNoPrivateKey when there not private key in config
|
// ErrConfigPrivateKeyRequired when there not private key in config
|
||||||
ErrConfigNoPrivateKey = errors.New("private key not defined in config")
|
ErrSignPrivateKeyRequired = errors.New("PrivateKey is required in config")
|
||||||
// ErrConfigNoDomain when there is no domain defined in config
|
|
||||||
ErrConfigNoDomain = errors.New("domain not defined 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