From e6f591e5e35d0fbb7910b615c22dda6f0709b250 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Depierrepont=20aka=20Toorop?= Date: Sun, 3 May 2015 16:03:02 +0200 Subject: [PATCH] Sign: check & sanitize config --- dkim.go | 106 ++++++++++++++++++++++++++++++++++++++++++++------- dkim_test.go | 39 +++++++++++++++++-- errors.go | 21 ++++++++-- 3 files changed, 145 insertions(+), 21 deletions(-) diff --git a/dkim.go b/dkim.go index d98f347..ec6fa85 100644 --- a/dkim.go +++ b/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 } diff --git a/dkim_test.go b/dkim_test.go index f50d857..7b0196e 100644 --- a/dkim_test.go +++ b/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())*/ } diff --git a/errors.go b/errors.go index 5e33429..11f4130 100644 --- a/errors.go +++ b/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") )