120 lines
2.6 KiB
Go
120 lines
2.6 KiB
Go
package dkim
|
|
|
|
import (
|
|
"bytes"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// sigOptions represents signing options
|
|
type sigOptions struct {
|
|
|
|
// 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
|
|
|
|
// 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{
|
|
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 && sanitize config
|
|
|
|
// PrivateKey (required & TODO: valid)
|
|
if options.PrivateKey == "" {
|
|
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
|
|
}
|