Merge branch 'master' of https://github.com/andres-erbsen/go-dkim
Conflicts: dkim.go dkim_test.go pubKeyRep.go
This commit is contained in:
17
README.md
17
README.md
@@ -1,13 +1,14 @@
|
|||||||
# go-dkim
|
# go-dkim
|
||||||
DKIM package for Golang
|
|
||||||
|
|
||||||
[](https://godoc.org/github.com/toorop/go-dkim)
|
Fork of `toorop`'s DKIM with non-pointer API and bugfixes.
|
||||||
|
|
||||||
|
[](https://godoc.org/github.com/andres-erbsen/go-dkim)
|
||||||
|
|
||||||
## Getting started
|
## Getting started
|
||||||
|
|
||||||
### Install
|
### Install
|
||||||
```
|
```
|
||||||
go get github.com/toorop/go-dkim
|
go get github.com/andres-erbsen/go-dkim
|
||||||
```
|
```
|
||||||
Warning: you need to use Go 1.4.2-master or 1.4.3 (when it will be available)
|
Warning: you need to use Go 1.4.2-master or 1.4.3 (when it will be available)
|
||||||
see https://github.com/golang/go/issues/10482 fro more info.
|
see https://github.com/golang/go/issues/10482 fro more info.
|
||||||
@@ -16,12 +17,13 @@ see https://github.com/golang/go/issues/10482 fro more info.
|
|||||||
|
|
||||||
```go
|
```go
|
||||||
import (
|
import (
|
||||||
dkim "github.com/toorop/go-dkim"
|
dkim "github.com/andres-erbsen/go-dkim"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main(){
|
func main(){
|
||||||
// email is the email to sign (byte slice)
|
// email is the email to sign (byte slice)
|
||||||
// privateKey the private key (pem encoded, byte slice )
|
// privateKey the private key (pem encoded, byte slice )
|
||||||
|
dkim := dkim.NewDkim()
|
||||||
options := dkim.NewSigOptions()
|
options := dkim.NewSigOptions()
|
||||||
options.PrivateKey = privateKey
|
options.PrivateKey = privateKey
|
||||||
options.Domain = "mydomain.tld"
|
options.Domain = "mydomain.tld"
|
||||||
@@ -33,8 +35,6 @@ func main(){
|
|||||||
options.Canonicalization = "relaxed/relaxed"
|
options.Canonicalization = "relaxed/relaxed"
|
||||||
err := dkim.Sign(&email, options)
|
err := dkim.Sign(&email, options)
|
||||||
// handle err..
|
// handle err..
|
||||||
|
|
||||||
// And... that's it, 'email' is signed ! Amazing© !!!
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -46,11 +46,12 @@ import (
|
|||||||
|
|
||||||
func main(){
|
func main(){
|
||||||
// email is the email to verify (byte slice)
|
// email is the email to verify (byte slice)
|
||||||
status, err := Verify(&email)
|
dkim := dkim.NewDkim()
|
||||||
// handle status, err (see godoc for status)
|
_, err := dkim.Verify(email)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Todo
|
## Todo
|
||||||
|
|
||||||
- [ ] handle z tag (copied header fields used for diagnostic use)
|
- [ ] handle z tag (copied header fields used for diagnostic use)
|
||||||
|
- [ ] handle multiple dns records
|
||||||
|
|||||||
134
dkim.go
134
dkim.go
@@ -17,6 +17,7 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
"net"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -26,17 +27,17 @@ const (
|
|||||||
MaxHeaderLineLength = 70
|
MaxHeaderLineLength = 70
|
||||||
)
|
)
|
||||||
|
|
||||||
type verifyOutput int
|
type dkim struct {
|
||||||
|
lookupTXT func(string) ([]string, error)
|
||||||
|
now func() time.Time
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
func NewDkim() *dkim {
|
||||||
SUCCESS verifyOutput = 1 + iota
|
return &dkim{
|
||||||
PERMFAIL
|
lookupTXT: net.LookupTXT,
|
||||||
TEMPFAIL
|
now: time.Now,
|
||||||
NOTSIGNED
|
}
|
||||||
TESTINGSUCCESS
|
}
|
||||||
TESTINGPERMFAIL
|
|
||||||
TESTINGTEMPFAIL
|
|
||||||
)
|
|
||||||
|
|
||||||
// sigOptions represents signing options
|
// sigOptions represents signing options
|
||||||
type SigOptions struct {
|
type SigOptions struct {
|
||||||
@@ -101,7 +102,7 @@ func (s *DefaultSigner) Sign(message []byte, algo string) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewSigOptions returns new sigoption with some defaults value
|
// NewSigOptions returns new sigoption with some defaults value
|
||||||
func NewSigOptions() SigOptions {
|
func (dkim *dkim) NewSigOptions() SigOptions {
|
||||||
return SigOptions{
|
return SigOptions{
|
||||||
Version: 1,
|
Version: 1,
|
||||||
Canonicalization: "simple/simple",
|
Canonicalization: "simple/simple",
|
||||||
@@ -130,32 +131,32 @@ func getPrivateKeyFromSigOptions(options SigOptions) (*rsa.PrivateKey, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Sign signs an email
|
// Sign signs an email
|
||||||
func Sign(email *[]byte, options SigOptions) error {
|
func (dkim *dkim) Sign(email []byte, options SigOptions) ([]byte, error) {
|
||||||
privateKey, err := getPrivateKeyFromSigOptions(options)
|
privateKey, err := getPrivateKeyFromSigOptions(options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Domain required
|
// Domain required
|
||||||
if options.Domain == "" {
|
if options.Domain == "" {
|
||||||
return ErrSignDomainRequired
|
return nil, ErrSignDomainRequired
|
||||||
}
|
}
|
||||||
|
|
||||||
// Selector required
|
// Selector required
|
||||||
if options.Selector == "" {
|
if options.Selector == "" {
|
||||||
return ErrSignSelectorRequired
|
return nil, ErrSignSelectorRequired
|
||||||
}
|
}
|
||||||
|
|
||||||
// Canonicalization
|
// Canonicalization
|
||||||
options.Canonicalization, err = validateCanonicalization(strings.ToLower(options.Canonicalization))
|
options.Canonicalization, err = validateCanonicalization(strings.ToLower(options.Canonicalization))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Algo
|
// Algo
|
||||||
options.Algo = strings.ToLower(options.Algo)
|
options.Algo = strings.ToLower(options.Algo)
|
||||||
if options.Algo != "rsa-sha1" && options.Algo != "rsa-sha256" {
|
if options.Algo != "rsa-sha1" && options.Algo != "rsa-sha256" {
|
||||||
return ErrSignBadAlgo
|
return nil, ErrSignBadAlgo
|
||||||
}
|
}
|
||||||
|
|
||||||
// Header must contain "from"
|
// Header must contain "from"
|
||||||
@@ -168,24 +169,23 @@ func Sign(email *[]byte, options SigOptions) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !hasFrom {
|
if !hasFrom {
|
||||||
return ErrSignHeaderShouldContainsFrom
|
return nil, ErrSignHeaderShouldContainsFrom
|
||||||
}
|
}
|
||||||
|
|
||||||
signer := &DefaultSigner{
|
signer := &DefaultSigner{
|
||||||
PrivateKey: privateKey,
|
PrivateKey: privateKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
dHeader, err := GetDkimHeader(*email, signer, &options)
|
dHeader, err := dkim.GetDkimHeader(email, signer, &options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
dHeader = "DKIM-Signature: " + dHeader + CRLF
|
dHeader = "DKIM-Signature: " + dHeader + CRLF
|
||||||
*email = append([]byte(dHeader), *email...)
|
return append([]byte(dHeader), email...), nil
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetDkimHeader(email []byte, signer Signer, options *SigOptions) (string, error) {
|
func (dkim *dkim) GetDkimHeader(email []byte, signer Signer, options *SigOptions) (string, error) {
|
||||||
headers, dHeader, err := getHashString(email, options)
|
headers, dHeader, err := getHashString(email, options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@@ -214,14 +214,14 @@ func GetDkimHeader(email []byte, signer Signer, options *SigOptions) (string, er
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getHashString(email []byte, options *SigOptions) (headers []byte, dheader string, err error) {
|
func getHashString(email []byte, options *SigOptions) (headers []byte, dheader string, err error) {
|
||||||
headers, body, err := canonicalize(&email, options.Canonicalization, options.Headers)
|
headers, body, err := canonicalize(email, options.Canonicalization, options.Headers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []byte{}, "", err
|
return []byte{}, "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
signHash := strings.Split(options.Algo, "-")
|
signHash := strings.Split(options.Algo, "-")
|
||||||
|
|
||||||
bodyHash, err := getBodyHash(&body, signHash[1], options.BodyLength)
|
bodyHash, err := getBodyHash(body, signHash[1], options.BodyLength)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []byte{}, "", err
|
return []byte{}, "", err
|
||||||
}
|
}
|
||||||
@@ -244,30 +244,28 @@ func getHashString(email []byte, options *SigOptions) (headers []byte, dheader s
|
|||||||
// state: SUCCESS or PERMFAIL or TEMPFAIL, TESTINGSUCCESS, TESTINGPERMFAIL
|
// state: SUCCESS or PERMFAIL or TEMPFAIL, TESTINGSUCCESS, TESTINGPERMFAIL
|
||||||
// TESTINGTEMPFAIL or NOTSIGNED
|
// TESTINGTEMPFAIL or NOTSIGNED
|
||||||
// error: if an error occurs during verification
|
// error: if an error occurs during verification
|
||||||
func Verify(email *[]byte) (verifyOutput, error) {
|
func (dkim *dkim) Verify(email []byte) (dkimHeader *DKIMHeader, err error) {
|
||||||
// parse email
|
dkimHeader, err = newDkimHeaderFromEmail(email)
|
||||||
dkimHeader, err := newDkimHeaderFromEmail(email)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == ErrDkimHeaderNotFound {
|
return
|
||||||
return NOTSIGNED, ErrDkimHeaderNotFound
|
|
||||||
}
|
|
||||||
return PERMFAIL, err
|
|
||||||
}
|
}
|
||||||
|
pubKeys, err := dkim.PubKeyFromDns(dkimHeader.Selector, dkimHeader.Domain)
|
||||||
// we do not set query method because if it's others, validation failed earlier
|
|
||||||
pubKeys, verifyOutputOnError, err := PubKeyFromDns(dkimHeader.Selector, dkimHeader.Domain)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return verifyOutputOnError, err
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(pubKeys) == 0 {
|
||||||
|
return nil, errors.New("Now pub keys found")
|
||||||
}
|
}
|
||||||
pubKey := pubKeys[0]
|
pubKey := pubKeys[0]
|
||||||
|
|
||||||
// Normalize
|
|
||||||
headers, body, err := canonicalize(email, dkimHeader.MessageCanonicalization, dkimHeader.Headers)
|
headers, body, err := canonicalize(email, dkimHeader.MessageCanonicalization, dkimHeader.Headers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return getVerifyOutput(PERMFAIL, err, pubKey.FlagTesting)
|
return nil, err
|
||||||
}
|
}
|
||||||
sigHash := strings.Split(dkimHeader.Algorithm, "-")
|
sigHash := strings.Split(dkimHeader.Algorithm, "-")
|
||||||
// check if hash algo are compatible
|
if len(sigHash) < 2 {
|
||||||
|
return nil, ErrVerifyInappropriateHashAlgo
|
||||||
|
}
|
||||||
compatible := false
|
compatible := false
|
||||||
for _, algo := range pubKey.HashAlgo {
|
for _, algo := range pubKey.HashAlgo {
|
||||||
if sigHash[1] == algo {
|
if sigHash[1] == algo {
|
||||||
@@ -276,58 +274,46 @@ func Verify(email *[]byte) (verifyOutput, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !compatible {
|
if !compatible {
|
||||||
return getVerifyOutput(PERMFAIL, ErrVerifyInappropriateHashAlgo, pubKey.FlagTesting)
|
return nil, ErrVerifyInappropriateHashAlgo
|
||||||
}
|
}
|
||||||
|
|
||||||
// expired ?
|
if !dkimHeader.SignatureExpiration.IsZero() {
|
||||||
if !dkimHeader.SignatureExpiration.IsZero() && dkimHeader.SignatureExpiration.Second() < time.Now().Second() {
|
if dkimHeader.SignatureExpiration.Before(dkim.now()) {
|
||||||
return getVerifyOutput(PERMFAIL, ErrVerifySignatureHasExpired, pubKey.FlagTesting)
|
return nil, ErrVerifySignatureHasExpired
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// get body hash
|
bodyHash, err := getBodyHash(body, sigHash[1], dkimHeader.BodyLength)
|
||||||
bodyHash, err := getBodyHash(&body, sigHash[1], dkimHeader.BodyLength)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return getVerifyOutput(PERMFAIL, err, pubKey.FlagTesting)
|
return nil, err
|
||||||
}
|
}
|
||||||
//println(bodyHash)
|
|
||||||
if bodyHash != dkimHeader.BodyHash {
|
if bodyHash != dkimHeader.BodyHash {
|
||||||
return getVerifyOutput(PERMFAIL, ErrVerifyBodyHash, pubKey.FlagTesting)
|
return nil, ErrVerifyBodyHash
|
||||||
}
|
}
|
||||||
|
|
||||||
// compute sig
|
// compute sig
|
||||||
dkimHeaderCano, err := canonicalizeHeader(dkimHeader.RawForSign, strings.Split(dkimHeader.MessageCanonicalization, "/")[0])
|
dkimHeaderCano, err := canonicalizeHeader(dkimHeader.RawForSign, strings.Split(dkimHeader.MessageCanonicalization, "/")[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return getVerifyOutput(TEMPFAIL, err, pubKey.FlagTesting)
|
return nil, err
|
||||||
}
|
}
|
||||||
toSignStr := string(headers) + dkimHeaderCano
|
toSignStr := string(headers) + dkimHeaderCano
|
||||||
toSign := bytes.TrimRight([]byte(toSignStr), " \r\n")
|
toSign := bytes.TrimRight([]byte(toSignStr), " \r\n")
|
||||||
|
|
||||||
err = verifySignature(toSign, dkimHeader.SignatureData, &pubKey.PubKey, sigHash[1])
|
err = verifySignature(toSign, dkimHeader.SignatureData, &pubKey.PubKey, sigHash[1])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return getVerifyOutput(PERMFAIL, err, pubKey.FlagTesting)
|
return nil, err
|
||||||
}
|
}
|
||||||
return SUCCESS, nil
|
if pubKey.FlagTesting {
|
||||||
|
err = ErrTesting
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// getVerifyOutput returns output of verify fct according to the testing flag
|
|
||||||
func getVerifyOutput(status verifyOutput, err error, flagTesting bool) (verifyOutput, error) {
|
|
||||||
if !flagTesting {
|
|
||||||
return status, err
|
|
||||||
}
|
|
||||||
switch status {
|
|
||||||
case SUCCESS:
|
|
||||||
return TESTINGSUCCESS, err
|
|
||||||
case PERMFAIL:
|
|
||||||
return TESTINGPERMFAIL, err
|
|
||||||
case TEMPFAIL:
|
|
||||||
return TESTINGTEMPFAIL, err
|
|
||||||
}
|
|
||||||
// should never happen but compilator sream whithout return
|
|
||||||
return status, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// canonicalize returns canonicalized version of header and body
|
// canonicalize returns canonicalized version of header and body
|
||||||
func canonicalize(email *[]byte, cano string, h []string) (headers, body []byte, err error) {
|
func canonicalize(email []byte, cano string, h []string) (headers, body []byte, err error) {
|
||||||
body = []byte{}
|
body = []byte{}
|
||||||
rxReduceWS := regexp.MustCompile(`[ \t]+`)
|
rxReduceWS := regexp.MustCompile(`[ \t]+`)
|
||||||
|
|
||||||
@@ -347,7 +333,6 @@ func canonicalize(email *[]byte, cano string, h []string) (headers, body []byte,
|
|||||||
match = nil
|
match = nil
|
||||||
headerToKeepToLower := strings.ToLower(headerToKeep)
|
headerToKeepToLower := strings.ToLower(headerToKeep)
|
||||||
for e := headersList.Front(); e != nil; e = e.Next() {
|
for e := headersList.Front(); e != nil; e = e.Next() {
|
||||||
//fmt.Printf("|%s|\n", e.Value.(string))
|
|
||||||
t := strings.Split(e.Value.(string), ":")
|
t := strings.Split(e.Value.(string), ":")
|
||||||
if strings.ToLower(t[0]) == headerToKeepToLower {
|
if strings.ToLower(t[0]) == headerToKeepToLower {
|
||||||
match = e
|
match = e
|
||||||
@@ -453,14 +438,14 @@ func canonicalizeHeader(header string, algo string) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// getBodyHash return the hash (bas64encoded) of the body
|
// getBodyHash return the hash (bas64encoded) of the body
|
||||||
func getBodyHash(body *[]byte, algo string, bodyLength uint) (string, error) {
|
func getBodyHash(body []byte, algo string, bodyLength uint) (string, error) {
|
||||||
var h hash.Hash
|
var h hash.Hash
|
||||||
if algo == "sha1" {
|
if algo == "sha1" {
|
||||||
h = sha1.New()
|
h = sha1.New()
|
||||||
} else {
|
} else {
|
||||||
h = sha256.New()
|
h = sha256.New()
|
||||||
}
|
}
|
||||||
toH := *body
|
toH := body
|
||||||
// if l tag (body length)
|
// if l tag (body length)
|
||||||
if bodyLength != 0 {
|
if bodyLength != 0 {
|
||||||
if uint(len(toH)) < bodyLength {
|
if uint(len(toH)) < bodyLength {
|
||||||
@@ -524,9 +509,10 @@ func verifySignature(toSign []byte, sig64 string, key *rsa.PublicKey, algo strin
|
|||||||
return rsa.VerifyPKCS1v15(key, h2, h1.Sum(nil), sig)
|
return rsa.VerifyPKCS1v15(key, h2, h1.Sum(nil), sig)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var rxReduceWS = regexp.MustCompile(`[ \t]+`)
|
||||||
|
|
||||||
// removeFWS removes all FWS from string
|
// removeFWS removes all FWS from string
|
||||||
func removeFWS(in string) string {
|
func removeFWS(in string) string {
|
||||||
rxReduceWS := regexp.MustCompile(`[ \t]+`)
|
|
||||||
out := strings.Replace(in, "\n", "", -1)
|
out := strings.Replace(in, "\n", "", -1)
|
||||||
out = strings.Replace(out, "\r", "", -1)
|
out = strings.Replace(out, "\r", "", -1)
|
||||||
out = rxReduceWS.ReplaceAllString(out, " ")
|
out = rxReduceWS.ReplaceAllString(out, " ")
|
||||||
@@ -574,9 +560,9 @@ func getHeadersList(rawHeader *[]byte) (*list.List, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// getHeadersBody return headers and body
|
// getHeadersBody return headers and body
|
||||||
func getHeadersBody(email *[]byte) ([]byte, []byte, error) {
|
func getHeadersBody(email []byte) ([]byte, []byte, error) {
|
||||||
// TODO: \n -> \r\n
|
// TODO: \n -> \r\n
|
||||||
parts := bytes.SplitN(*email, []byte{13, 10, 13, 10}, 2)
|
parts := bytes.SplitN(email, []byte{13, 10, 13, 10}, 2)
|
||||||
if len(parts) != 2 {
|
if len(parts) != 2 {
|
||||||
return []byte{}, []byte{}, ErrBadMailFormat
|
return []byte{}, []byte{}, ErrBadMailFormat
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type dkimHeader struct {
|
// DKIMHeader
|
||||||
|
type DKIMHeader struct {
|
||||||
// Version This tag defines the version of DKIM
|
// Version This tag defines the version of DKIM
|
||||||
// specification that applies to the signature record.
|
// specification that applies to the signature record.
|
||||||
// tag v
|
// tag v
|
||||||
@@ -99,7 +100,7 @@ type dkimHeader struct {
|
|||||||
// Internationalized domain names MUST be encoded as A-labels, as
|
// Internationalized domain names MUST be encoded as A-labels, as
|
||||||
// described in Section 2.3 of [RFC5890].
|
// described in Section 2.3 of [RFC5890].
|
||||||
// tag i
|
// tag i
|
||||||
Auid string
|
AUID string
|
||||||
|
|
||||||
// Body length count (plain-text unsigned decimal integer; OPTIONAL,
|
// Body length count (plain-text unsigned decimal integer; OPTIONAL,
|
||||||
// default is entire body). This tag informs the Verifier of the
|
// default is entire body). This tag informs the Verifier of the
|
||||||
@@ -197,14 +198,14 @@ type dkimHeader struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewDkimHeaderBySigOptions return a new DkimHeader initioalized with sigOptions value
|
// NewDkimHeaderBySigOptions return a new DkimHeader initioalized with sigOptions value
|
||||||
func newDkimHeaderBySigOptions(options SigOptions) *dkimHeader {
|
func newDkimHeaderBySigOptions(options SigOptions) *DKIMHeader {
|
||||||
h := new(dkimHeader)
|
h := new(DKIMHeader)
|
||||||
h.Version = "1"
|
h.Version = "1"
|
||||||
h.Algorithm = options.Algo
|
h.Algorithm = options.Algo
|
||||||
h.MessageCanonicalization = options.Canonicalization
|
h.MessageCanonicalization = options.Canonicalization
|
||||||
h.Domain = options.Domain
|
h.Domain = options.Domain
|
||||||
h.Headers = options.Headers
|
h.Headers = options.Headers
|
||||||
h.Auid = options.Auid
|
h.AUID = options.Auid
|
||||||
h.BodyLength = options.BodyLength
|
h.BodyLength = options.BodyLength
|
||||||
h.QueryMethods = options.QueryMethods
|
h.QueryMethods = options.QueryMethods
|
||||||
h.Selector = options.Selector
|
h.Selector = options.Selector
|
||||||
@@ -221,8 +222,8 @@ func newDkimHeaderBySigOptions(options SigOptions) *dkimHeader {
|
|||||||
// NewFromEmail return a new DkimHeader by parsing an email
|
// NewFromEmail return a new DkimHeader by parsing an email
|
||||||
// Note: according to RFC 6376 an email can have multiple DKIM Header
|
// Note: according to RFC 6376 an email can have multiple DKIM Header
|
||||||
// in this case we return the last inserted or the last with d== mail from
|
// in this case we return the last inserted or the last with d== mail from
|
||||||
func newDkimHeaderFromEmail(email *[]byte) (*dkimHeader, error) {
|
func newDkimHeaderFromEmail(email []byte) (*DKIMHeader, error) {
|
||||||
m, err := mail.ReadMessage(bytes.NewReader(*email))
|
m, err := mail.ReadMessage(bytes.NewReader(email))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -265,7 +266,7 @@ func newDkimHeaderFromEmail(email *[]byte) (*dkimHeader, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var keep *dkimHeader
|
var keep *DKIMHeader
|
||||||
var keepErr error
|
var keepErr error
|
||||||
//for _, dk := range m.Header[textproto.CanonicalMIMEHeaderKey("DKIM-Signature")] {
|
//for _, dk := range m.Header[textproto.CanonicalMIMEHeaderKey("DKIM-Signature")] {
|
||||||
for _, h := range dkHeaders {
|
for _, h := range dkHeaders {
|
||||||
@@ -291,8 +292,8 @@ func newDkimHeaderFromEmail(email *[]byte) (*dkimHeader, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// parseDkHeader parse raw dkim header
|
// parseDkHeader parse raw dkim header
|
||||||
func parseDkHeader(header string) (dkh *dkimHeader, err error) {
|
func parseDkHeader(header string) (dkh *DKIMHeader, err error) {
|
||||||
dkh = new(dkimHeader)
|
dkh = new(DKIMHeader)
|
||||||
|
|
||||||
keyVal := strings.SplitN(header, ":", 2)
|
keyVal := strings.SplitN(header, ":", 2)
|
||||||
|
|
||||||
@@ -389,7 +390,7 @@ func parseDkHeader(header string) (dkh *dkimHeader, err error) {
|
|||||||
if !strings.HasSuffix(data, dkh.Domain) {
|
if !strings.HasSuffix(data, dkh.Domain) {
|
||||||
return nil, ErrDkimHeaderDomainMismatch
|
return nil, ErrDkimHeaderDomainMismatch
|
||||||
}
|
}
|
||||||
dkh.Auid = data
|
dkh.AUID = data
|
||||||
}
|
}
|
||||||
case "l":
|
case "l":
|
||||||
ui, err := strconv.ParseUint(data, 10, 32)
|
ui, err := strconv.ParseUint(data, 10, 32)
|
||||||
@@ -428,8 +429,8 @@ func parseDkHeader(header string) (dkh *dkimHeader, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// default for i/Auid
|
// default for i/Auid
|
||||||
if dkh.Auid == "" {
|
if dkh.AUID == "" {
|
||||||
dkh.Auid = "@" + dkh.Domain
|
dkh.AUID = "@" + dkh.Domain
|
||||||
}
|
}
|
||||||
|
|
||||||
// defaut for query method
|
// defaut for query method
|
||||||
@@ -443,7 +444,7 @@ func parseDkHeader(header string) (dkh *dkimHeader, err error) {
|
|||||||
|
|
||||||
// GetHeaderBase return base header for signers
|
// GetHeaderBase return base header for signers
|
||||||
// Todo: some refactoring needed...
|
// Todo: some refactoring needed...
|
||||||
func (d *dkimHeader) getHeaderBaseForSigning(bodyHash string) string {
|
func (d *DKIMHeader) getHeaderBaseForSigning(bodyHash string) string {
|
||||||
h := "DKIM-Signature: v=" + d.Version + "; a=" + d.Algorithm + "; q=" + strings.Join(d.QueryMethods, ":") + "; c=" + d.MessageCanonicalization + ";" + CRLF + TAB
|
h := "DKIM-Signature: v=" + d.Version + "; a=" + d.Algorithm + "; q=" + strings.Join(d.QueryMethods, ":") + "; c=" + d.MessageCanonicalization + ";" + CRLF + TAB
|
||||||
subh := "s=" + d.Selector + ";"
|
subh := "s=" + d.Selector + ";"
|
||||||
if len(subh)+len(d.Domain)+4 > MaxHeaderLineLength {
|
if len(subh)+len(d.Domain)+4 > MaxHeaderLineLength {
|
||||||
@@ -453,12 +454,12 @@ func (d *dkimHeader) getHeaderBaseForSigning(bodyHash string) string {
|
|||||||
subh += " d=" + d.Domain + ";"
|
subh += " d=" + d.Domain + ";"
|
||||||
|
|
||||||
// Auid
|
// Auid
|
||||||
if len(d.Auid) != 0 {
|
if len(d.AUID) != 0 {
|
||||||
if len(subh)+len(d.Auid)+4 > MaxHeaderLineLength {
|
if len(subh)+len(d.AUID)+4 > MaxHeaderLineLength {
|
||||||
h += subh + FWS
|
h += subh + FWS
|
||||||
subh = ""
|
subh = ""
|
||||||
}
|
}
|
||||||
subh += " i=" + d.Auid + ";"
|
subh += " i=" + d.AUID + ";"
|
||||||
}
|
}
|
||||||
|
|
||||||
/*h := "DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=tmail.io; i=@tmail.io;" + FWS
|
/*h := "DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=tmail.io; i=@tmail.io;" + FWS
|
||||||
|
|||||||
181
dkim_test.go
181
dkim_test.go
@@ -1,9 +1,13 @@
|
|||||||
package dkim
|
package dkim
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
//"fmt"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -202,96 +206,103 @@ var missingHeaderMail = "Received: tmail deliverd remote 439903a23facd153908f3e1
|
|||||||
"test"
|
"test"
|
||||||
|
|
||||||
func Test_NewSigOptions(t *testing.T) {
|
func Test_NewSigOptions(t *testing.T) {
|
||||||
options := NewSigOptions()
|
dkim := NewDkim()
|
||||||
|
options := dkim.NewSigOptions()
|
||||||
assert.Equal(t, "rsa-sha256", options.Algo)
|
assert.Equal(t, "rsa-sha256", options.Algo)
|
||||||
assert.Equal(t, "simple/simple", options.Canonicalization)
|
assert.Equal(t, "simple/simple", options.Canonicalization)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_SignConfig(t *testing.T) {
|
func Test_SignConfig(t *testing.T) {
|
||||||
|
dkim := NewDkim()
|
||||||
|
|
||||||
email := []byte(emailBase)
|
email := []byte(emailBase)
|
||||||
emailToTest := append([]byte(nil), email...)
|
emailToTest := append([]byte(nil), email...)
|
||||||
options := NewSigOptions()
|
options := dkim.NewSigOptions()
|
||||||
err := Sign(&emailToTest, options)
|
_, err := dkim.Sign(emailToTest, options)
|
||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
// && err No private key
|
// && err No private key
|
||||||
assert.EqualError(t, err, ErrSignPrivateKeyRequired.Error())
|
assert.EqualError(t, err, ErrSignPrivateKeyRequired.Error())
|
||||||
options.PrivateKey = []byte(privKey)
|
options.PrivateKey = []byte(privKey)
|
||||||
emailToTest = append([]byte(nil), email...)
|
emailToTest = append([]byte(nil), email...)
|
||||||
err = Sign(&emailToTest, options)
|
_, err = dkim.Sign(emailToTest, options)
|
||||||
|
|
||||||
// Domain
|
// Domain
|
||||||
assert.EqualError(t, err, ErrSignDomainRequired.Error())
|
assert.EqualError(t, err, ErrSignDomainRequired.Error())
|
||||||
options.Domain = "toorop.fr"
|
options.Domain = "toorop.fr"
|
||||||
emailToTest = append([]byte(nil), email...)
|
emailToTest = append([]byte(nil), email...)
|
||||||
err = Sign(&emailToTest, options)
|
_, err = dkim.Sign(emailToTest, options)
|
||||||
|
|
||||||
// Selector
|
// Selector
|
||||||
assert.Error(t, err, ErrSignSelectorRequired.Error())
|
assert.Error(t, err, ErrSignSelectorRequired.Error())
|
||||||
options.Selector = "default"
|
options.Selector = "default"
|
||||||
emailToTest = append([]byte(nil), email...)
|
emailToTest = append([]byte(nil), email...)
|
||||||
err = Sign(&emailToTest, options)
|
_, err = dkim.Sign(emailToTest, options)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
// Canonicalization
|
// Canonicalization
|
||||||
options.Canonicalization = "simple/relaxed/simple"
|
options.Canonicalization = "simple/relaxed/simple"
|
||||||
emailToTest = append([]byte(nil), email...)
|
emailToTest = append([]byte(nil), email...)
|
||||||
err = Sign(&emailToTest, options)
|
_, err = dkim.Sign(emailToTest, options)
|
||||||
assert.EqualError(t, err, ErrSignBadCanonicalization.Error())
|
assert.EqualError(t, err, ErrSignBadCanonicalization.Error())
|
||||||
|
|
||||||
options.Canonicalization = "simple/relax"
|
options.Canonicalization = "simple/relax"
|
||||||
emailToTest = append([]byte(nil), email...)
|
emailToTest = append([]byte(nil), email...)
|
||||||
err = Sign(&emailToTest, options)
|
_, err = dkim.Sign(emailToTest, options)
|
||||||
assert.EqualError(t, err, ErrSignBadCanonicalization.Error())
|
assert.EqualError(t, err, ErrSignBadCanonicalization.Error())
|
||||||
|
|
||||||
options.Canonicalization = "relaxed"
|
options.Canonicalization = "relaxed"
|
||||||
emailToTest = append([]byte(nil), email...)
|
emailToTest = append([]byte(nil), email...)
|
||||||
err = Sign(&emailToTest, options)
|
_, err = dkim.Sign(emailToTest, options)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
options.Canonicalization = "SiMple/relAxed"
|
options.Canonicalization = "SiMple/relAxed"
|
||||||
emailToTest = append([]byte(nil), email...)
|
emailToTest = append([]byte(nil), email...)
|
||||||
err = Sign(&emailToTest, options)
|
_, err = dkim.Sign(emailToTest, options)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
// header
|
// header
|
||||||
options.Headers = []string{"toto"}
|
options.Headers = []string{"toto"}
|
||||||
emailToTest = append([]byte(nil), email...)
|
emailToTest = append([]byte(nil), email...)
|
||||||
err = Sign(&emailToTest, options)
|
_, err = dkim.Sign(emailToTest, options)
|
||||||
assert.EqualError(t, err, ErrSignHeaderShouldContainsFrom.Error())
|
assert.EqualError(t, err, ErrSignHeaderShouldContainsFrom.Error())
|
||||||
|
|
||||||
options.Headers = []string{"To", "From"}
|
options.Headers = []string{"To", "From"}
|
||||||
emailToTest = append([]byte(nil), email...)
|
emailToTest = append([]byte(nil), email...)
|
||||||
err = Sign(&emailToTest, options)
|
_, err = dkim.Sign(emailToTest, options)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_canonicalize(t *testing.T) {
|
func Test_canonicalize(t *testing.T) {
|
||||||
|
dkim := NewDkim()
|
||||||
|
|
||||||
email := []byte(emailBase)
|
email := []byte(emailBase)
|
||||||
emailToTest := append([]byte(nil), email...)
|
emailToTest := append([]byte(nil), email...)
|
||||||
options := NewSigOptions()
|
options := dkim.NewSigOptions()
|
||||||
options.Headers = []string{"from", "date", "mime-version", "received", "received", "In-Reply-To"}
|
options.Headers = []string{"from", "date", "mime-version", "received", "received", "In-Reply-To"}
|
||||||
// simple/simple
|
// simple/simple
|
||||||
options.Canonicalization = "simple/simple"
|
options.Canonicalization = "simple/simple"
|
||||||
header, body, err := canonicalize(&emailToTest, options.Canonicalization, options.Headers)
|
header, body, err := canonicalize(emailToTest, options.Canonicalization, options.Headers)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, []byte(headerSimple), header)
|
assert.Equal(t, headerSimple, string(header))
|
||||||
assert.Equal(t, []byte(bodySimple), body)
|
assert.Equal(t, bodySimple, string(body))
|
||||||
|
|
||||||
// relaxed/relaxed
|
// relaxed/relaxed
|
||||||
emailToTest = append([]byte(nil), email...)
|
emailToTest = append([]byte(nil), email...)
|
||||||
options.Canonicalization = "relaxed/relaxed"
|
options.Canonicalization = "relaxed/relaxed"
|
||||||
header, body, err = canonicalize(&emailToTest, options.Canonicalization, options.Headers)
|
header, body, err = canonicalize(emailToTest, options.Canonicalization, options.Headers)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, []byte(headerRelaxed), header)
|
assert.Equal(t, headerRelaxed, string(header))
|
||||||
assert.Equal(t, []byte(bodyRelaxed), body)
|
assert.Equal(t, bodyRelaxed, string(body))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_Sign(t *testing.T) {
|
func Test_Sign(t *testing.T) {
|
||||||
|
dkim := NewDkim()
|
||||||
|
|
||||||
email := []byte(emailBase)
|
email := []byte(emailBase)
|
||||||
emailRelaxed := append([]byte(nil), email...)
|
emailRelaxed := append([]byte(nil), email...)
|
||||||
options := NewSigOptions()
|
options := dkim.NewSigOptions()
|
||||||
options.PrivateKey = []byte(privKey)
|
options.PrivateKey = []byte(privKey)
|
||||||
options.Domain = domain
|
options.Domain = domain
|
||||||
options.Selector = selector
|
options.Selector = selector
|
||||||
@@ -300,77 +311,157 @@ func Test_Sign(t *testing.T) {
|
|||||||
options.AddSignatureTimestamp = false
|
options.AddSignatureTimestamp = false
|
||||||
|
|
||||||
options.Canonicalization = "relaxed/relaxed"
|
options.Canonicalization = "relaxed/relaxed"
|
||||||
err := Sign(&emailRelaxed, options)
|
emailRelaxed, err := dkim.Sign(emailRelaxed, options)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, []byte(signedRelaxedRelaxed), emailRelaxed)
|
assert.Equal(t, signedRelaxedRelaxed, string(emailRelaxed))
|
||||||
|
|
||||||
options.BodyLength = 5
|
options.BodyLength = 5
|
||||||
emailRelaxed = append([]byte(nil), email...)
|
emailRelaxed = append([]byte(nil), email...)
|
||||||
err = Sign(&emailRelaxed, options)
|
emailRelaxed, err = dkim.Sign(emailRelaxed, options)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, []byte(signedRelaxedRelaxedLength), emailRelaxed)
|
assert.Equal(t, signedRelaxedRelaxedLength, string(emailRelaxed))
|
||||||
|
|
||||||
options.BodyLength = 0
|
options.BodyLength = 0
|
||||||
options.Canonicalization = "simple/simple"
|
options.Canonicalization = "simple/simple"
|
||||||
emailSimple := append([]byte(nil), email...)
|
emailSimple := append([]byte(nil), email...)
|
||||||
err = Sign(&emailSimple, options)
|
emailSimple, err = dkim.Sign(emailSimple, options)
|
||||||
assert.Equal(t, []byte(signedSimpleSimple), emailSimple)
|
assert.Equal(t, signedSimpleSimple, string(emailSimple))
|
||||||
|
|
||||||
options.Headers = []string{"from", "subject", "date", "message-id"}
|
options.Headers = []string{"from", "subject", "date", "message-id"}
|
||||||
memail := []byte(missingHeaderMail)
|
memail := []byte(missingHeaderMail)
|
||||||
err = Sign(&memail, options)
|
_, err = dkim.Sign(memail, options)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
options.BodyLength = 5
|
options.BodyLength = 5
|
||||||
options.Canonicalization = "simple/simple"
|
options.Canonicalization = "simple/simple"
|
||||||
emailSimple = append([]byte(nil), email...)
|
emailSimple = append([]byte(nil), email...)
|
||||||
err = Sign(&emailSimple, options)
|
emailSimple, err = dkim.Sign(emailSimple, options)
|
||||||
assert.Equal(t, []byte(signedSimpleSimpleLength), emailSimple)
|
assert.Equal(t, signedSimpleSimpleLength, string(emailSimple))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_Verify(t *testing.T) {
|
func Test_Verify(t *testing.T) {
|
||||||
|
dkim := NewDkim()
|
||||||
|
|
||||||
// no DKIM header
|
// no DKIM header
|
||||||
email := []byte(emailBase)
|
email := []byte(emailBase)
|
||||||
status, err := Verify(&email)
|
_, err := dkim.Verify(email)
|
||||||
assert.Equal(t, NOTSIGNED, status)
|
|
||||||
assert.Equal(t, ErrDkimHeaderNotFound, err)
|
assert.Equal(t, ErrDkimHeaderNotFound, err)
|
||||||
|
|
||||||
// No From
|
// No From
|
||||||
email = []byte(signedNoFrom)
|
email = []byte(signedNoFrom)
|
||||||
status, err = Verify(&email)
|
_, err = dkim.Verify(email)
|
||||||
assert.Equal(t, ErrVerifyBodyHash, err)
|
assert.Equal(t, ErrVerifyBodyHash, err)
|
||||||
assert.Equal(t, TESTINGPERMFAIL, status) // cause we use dkheader of the "with from" email
|
|
||||||
|
|
||||||
// missing mandatory 'a' flag
|
// missing mandatory 'a' flag
|
||||||
email = []byte(signedMissingFlag)
|
email = []byte(signedMissingFlag)
|
||||||
status, err = Verify(&email)
|
_, err = dkim.Verify(email)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.Equal(t, PERMFAIL, status)
|
|
||||||
assert.Equal(t, ErrDkimHeaderMissingRequiredTag, err)
|
assert.Equal(t, ErrDkimHeaderMissingRequiredTag, err)
|
||||||
|
|
||||||
// missing bad algo
|
// missing bad algo
|
||||||
email = []byte(signedBadAlgo)
|
email = []byte(signedBadAlgo)
|
||||||
status, err = Verify(&email)
|
_, err = dkim.Verify(email)
|
||||||
assert.Equal(t, PERMFAIL, status)
|
|
||||||
assert.Equal(t, ErrSignBadAlgo, err)
|
assert.Equal(t, ErrSignBadAlgo, err)
|
||||||
|
|
||||||
// relaxed
|
// relaxed
|
||||||
email = []byte(signedRelaxedRelaxedLength)
|
email = []byte(signedRelaxedRelaxedLength)
|
||||||
status, err = Verify(&email)
|
_, err = dkim.Verify(email)
|
||||||
assert.NoError(t, err)
|
assert.Equal(t, ErrTesting, err)
|
||||||
assert.Equal(t, SUCCESS, status)
|
|
||||||
|
|
||||||
// simple
|
// simple
|
||||||
email = []byte(signedSimpleSimpleLength)
|
email = []byte(signedSimpleSimpleLength)
|
||||||
status, err = Verify(&email)
|
_, err = dkim.Verify(email)
|
||||||
assert.NoError(t, err)
|
assert.Equal(t, ErrTesting, err)
|
||||||
assert.Equal(t, SUCCESS, status)
|
|
||||||
|
|
||||||
// gmail
|
// gmail
|
||||||
email = []byte(fromGmail)
|
email = []byte(fromGmail)
|
||||||
status, err = Verify(&email)
|
_, err = dkim.Verify(email)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, SUCCESS, status)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var yahooIncDKIMtest = strings.Replace(`X-Apparently-To: andreser@yahoo-inc.com; Mon, 17 Aug 2015 22:49:25 +0000
|
||||||
|
Return-Path: <andreser@yahoo-inc.com>
|
||||||
|
Received-SPF: pass (domain of yahoo-inc.com designates 216.145.54.109 as permitted sender)
|
||||||
|
X-YMailISG: mjZhcf4WLDsyM65yWRfgyfO_lZT.dRW6ZkL0mQ36QKSZ1wt8
|
||||||
|
norPyPfS_RaocAsatZMUc76bWB9uuFubtxIu.6wHOaop_IvkFzIxMpIj0qV.
|
||||||
|
Lrx.L7iOLJ2Y5WVt6viLV7QS58O_2NzGwj3OIQL5EkGvSAZntHzX6fwew2_o
|
||||||
|
mtpmgrO9DKSOmSxs0mI1hgXdqr2U2oqrtF9ibc4Z2cFMaZ4R1JeYcprQW9Xu
|
||||||
|
X0YqkidSky.VEpst35uNTE.OMGZrIFHPzaKfF5GarnIJGSqhk.5NMjq_Bywg
|
||||||
|
5LYpX9AoXaCFOQd0Tzp4raM0IUmhBRaGPPXUBbzqovVvuLdJ.clh6.kYtv_F
|
||||||
|
5aNQtHP5cNqhPTooi1c_mZlh6phP12PMUVdx9WdfEmvVaN1Jumay.SzOtTPh
|
||||||
|
89IA7pgAferCuLh5f_9lEkYLkFomW4SRwexAbpdfwm1R1CYprsZMQ1YhFZI3
|
||||||
|
GinHyEiPUo48hxgTJgWIuv0oiCoDzd8exD5.u0ZW6Ztvy3UVvogbGCJ6KvXy
|
||||||
|
7CT1iwdHcoCiGcoE9e7zEqZdH7GftkZGobaX83r3bzhhc0GVMmY29fB4BnZj
|
||||||
|
suHtpK.Cx7vY.hJvV_R_.QH5npxcM8ptVFLgkNW6tBzqF9GnbWtr7v2ERGjn
|
||||||
|
hewHjiEQAGbay6c19tw.3s0SEEhb0BdbxeGajeqNJhYLC8j18hRQR67oWyFF
|
||||||
|
LON7S1cfRM2sQKVWW4K0I7KMad7FrxEi6VJdfIVD8gLMW7uhlkowqOE9rhtj
|
||||||
|
042FEnYc7kcrvL58Bj8v9TY3Z2Nl8HXifr6dGK_Kw9HK79We3O00cdZSWASu
|
||||||
|
R8pA_AB40d80d82.0crHu0oFFX6KFT8xkAipyIvPhK4bZz7r.NnBD1ZKq7ZF
|
||||||
|
TpBmxt0hbxWy_Qkz1M9BrzrGbbeSAFhAyyZqoPYsWy8FN5U3jzU.ZQygaK.E
|
||||||
|
DT18hIHBF2qN4R3JLVxA7zX1OfxL24UlPvuPaAERm9Wq4WRagcK7ysJt7.9b
|
||||||
|
WskH.vySl_.3mtF7yBFXOR_7_aIM54djcILP_MGhqEjJVbPp12KmbQ51cD_o
|
||||||
|
76mHVraxIkOZV0eVal8V9QwIaAbbb9caFJcySJdUSIVvojxd6fneN83jCsD.
|
||||||
|
Df0Iz4J2pF0BYiVnY4.MIhUSZZtCjxBAK4roNSvdyVDEQdYPiJpQHoBIDCnj
|
||||||
|
mgQCRPWOCRXaaDMnFvBzJJ7_z04R5rB3vFg65xsBN0wyeDX1veLLsMHChAbp
|
||||||
|
8RPEQFrsqmFFrXXRODtacXX1ZOZV1tTI
|
||||||
|
X-Originating-IP: [216.145.54.109]
|
||||||
|
Authentication-Results: mta2007.corp.mail.ne1.yahoo.com from=yahoo-inc.com; domainkeys=neutral (no sig); from=yahoo-inc.com; dkim=pass (ok)
|
||||||
|
Received: from 127.0.0.1 (EHLO mrout4.yahoo.com) (216.145.54.109)
|
||||||
|
by mta2007.corp.mail.ne1.yahoo.com with SMTPS; Mon, 17 Aug 2015 22:49:25 +0000
|
||||||
|
Received: from omp1017.mail.ne1.yahoo.com (omp1017.mail.ne1.yahoo.com [98.138.89.161])
|
||||||
|
by mrout4.yahoo.com (8.14.9/8.14.9/y.out) with ESMTP id t7HMn6pT004450
|
||||||
|
(version=TLSv1/SSLv3 cipher=DHE-RSA-CAMELLIA256-SHA bits=256 verify=NO)
|
||||||
|
for <andreser@yahoo-inc.com>; Mon, 17 Aug 2015 15:49:06 -0700 (PDT)
|
||||||
|
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=yahoo-inc.com;
|
||||||
|
s=cobra; t=1439851746;
|
||||||
|
bh=Zg0pSZvCcMHE9S9qpkoEKeacBIM4T3Xu4TUSMEL4rXw=;
|
||||||
|
h=Date:From:Reply-To:To:Subject;
|
||||||
|
b=C+cq+oEeDf+21aR1gaYIeuqE9cwJBuT3leqtd1ktLtmd4R3HAWXkt8Wr18PeOicjT
|
||||||
|
+8IJeZ4t+D6UDq3cIHRblyK2+LRP514YDttLfNbQQ28BOlEaycS4ZbrRtwYR0/bJsJ
|
||||||
|
EekQ8FrwzHZQOlmrqeN4bVIAlI73X+OBynbLyDrw=
|
||||||
|
Received: (qmail 15334 invoked by uid 1000); 17 Aug 2015 22:49:06 -0000
|
||||||
|
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=yahoo-inc.com; s=ginc1024; t=1439851746; bh=Zg0pSZvCcMHE9S9qpkoEKeacBIM4T3Xu4TUSMEL4rXw=; h=Date:From:Reply-To:To:Message-ID:Subject:MIME-Version:Content-Type; b=Ut+tXUluIOFrGnFm6m0fvXuIQDIEulXFkWmj9bQSO0JN3gPiWfuh1bFhZBdnu2C4SREtTfrxHI8q5DGPjD8yg4LnxFh3HOuaf4Ttm8w72QGO1HxJCdwkNvu5W4mnFTEB8hdl2u5naE4JqjJtM291ZYIJGvxFA2J3+Snj/N2aG40=
|
||||||
|
X-YMail-OSG: G1B4VdwVM1lYA9kmxoxrGwEODiHeae6vbYVeBm754R2VWrC5KBM9pyd4ojSurOA
|
||||||
|
q0um_rXRvGr1aqpHntt5GL5mcITy4qZFZWIBKRlGdOvQKNsKMSzsglbrG0Io._.0dI8XBQ.DNWG3
|
||||||
|
Z5uVt9prZqJLlJG.FcGrNnYQTiX.Q0HDTID4rDKM.sA6Z_CUAPOto0IFqnA9buS5R8Rjy3xqs5qf
|
||||||
|
krxUdQCFbVG.ML8Kl0WJfy8ZKxjg1mT7Nma.ZOA--
|
||||||
|
Received: by 98.138.105.251; Mon, 17 Aug 2015 22:49:05 +0000
|
||||||
|
Date: Mon, 17 Aug 2015 22:49:05 +0000 (UTC)
|
||||||
|
From: Andres Erbsen <andreser@yahoo-inc.com>
|
||||||
|
Reply-To: Andres Erbsen <andreser@yahoo-inc.com>
|
||||||
|
To: Andres Erbsen Erbsen <andreser@yahoo-inc.com>
|
||||||
|
Message-ID: <408588803.6263873.1439851745104.JavaMail.yahoo@mail.yahoo.com>
|
||||||
|
Subject: end-to-end public key verification [test]
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: multipart/alternative;
|
||||||
|
boundary="----=_Part_6263872_19047179.1439851745102"
|
||||||
|
Content-Length: 622
|
||||||
|
|
||||||
|
------=_Part_6263872_19047179.1439851745102
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
Content-Transfer-Encoding: 7bit
|
||||||
|
|
||||||
|
fdsfasdfdasgawgasdgdsgadfgadsgdgadga
|
||||||
|
------=_Part_6263872_19047179.1439851745102
|
||||||
|
Content-Type: text/html; charset=UTF-8
|
||||||
|
Content-Transfer-Encoding: 7bit
|
||||||
|
|
||||||
|
<html><body><div style="color:#000; background-color:#fff; font-family:HelveticaNeue-Light, Helvetica Neue Light, Helvetica Neue, Helvetica, Arial, Lucida Grande, sans-serif;font-size:16px"><div id="yui_3_16_0_1_1439835732243_26145" dir="ltr">fdsfasdfdasgawgasdgdsgadfgadsgdgadga</div></div></body></html>
|
||||||
|
------=_Part_6263872_19047179.1439851745102--`, "\n", "\r\n", -1)
|
||||||
|
|
||||||
|
func TestYahooIncDKIM(t *testing.T) {
|
||||||
|
dkim := NewDkim()
|
||||||
|
dkim.lookupTXT = func(string) ([]string, error) {
|
||||||
|
return []string{"v=DKIM1; g=*; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDGDd1Fz/AblN4d1haW+4B/u8PXkpd/s/JFkCPqp0Zk8xZ/SEs15fsWmj7yZwfsgi04Bs1eJhUIGf0iufHvkK5ws5XKBfbw1hYBHexopqYT5JFERYJ3slNEG5EeB04kKWpECjoMkXhDWvUJrHaBqGAz2KQ1dKAzrtKqRN2IVcDbBQIDAQAB"}, nil
|
||||||
|
}
|
||||||
|
//dkim.now = func() time.Time { return time.Unix(1439925628, 0) }
|
||||||
|
//_, err := dkim.Verify([]byte(yahooIncDKIMtest), yIncTXT, yIncTime)
|
||||||
|
_, err := dkim.Verify([]byte(yahooIncDKIMtest))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(time.Now())
|
||||||
|
}
|
||||||
|
|||||||
@@ -65,7 +65,8 @@ var (
|
|||||||
// ErrVerifyNoKeyForSignature no key
|
// ErrVerifyNoKeyForSignature no key
|
||||||
ErrVerifyNoKeyForSignature = errors.New("no key for verify")
|
ErrVerifyNoKeyForSignature = errors.New("no key for verify")
|
||||||
|
|
||||||
// ErrVerifyKeyUnavailable when service (dns) is anavailable
|
// ErrVerifyKeyUnavailable when service (dns) is anavailable.
|
||||||
|
// This error may be temporary in some cases.
|
||||||
ErrVerifyKeyUnavailable = errors.New("key unavailable")
|
ErrVerifyKeyUnavailable = errors.New("key unavailable")
|
||||||
|
|
||||||
// ErrVerifyTagVMustBeTheFirst if present the v tag must be the firts in the record
|
// ErrVerifyTagVMustBeTheFirst if present the v tag must be the firts in the record
|
||||||
@@ -91,4 +92,7 @@ var (
|
|||||||
|
|
||||||
// ErrVerifyInappropriateHashAlgo when h tag in pub key doesn't contain hash algo from a tag of DKIM header
|
// ErrVerifyInappropriateHashAlgo when h tag in pub key doesn't contain hash algo from a tag of DKIM header
|
||||||
ErrVerifyInappropriateHashAlgo = errors.New("inappropriate has algorithm")
|
ErrVerifyInappropriateHashAlgo = errors.New("inappropriate has algorithm")
|
||||||
|
|
||||||
|
// ErrTesting
|
||||||
|
ErrTesting = errors.New("public key has testing flag set")
|
||||||
)
|
)
|
||||||
|
|||||||
26
pubKeyRep.go
26
pubKeyRep.go
@@ -4,7 +4,6 @@ import (
|
|||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"net"
|
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -21,19 +20,18 @@ type PubKey struct {
|
|||||||
Selector string
|
Selector string
|
||||||
}
|
}
|
||||||
|
|
||||||
func PubKeyFromDns(selector, domain string) ([]*PubKey, verifyOutput, error) {
|
func (dkim *dkim) PubKeyFromDns(selector, domain string) ([]*PubKey, error) {
|
||||||
txt, err := net.LookupTXT(selector + "._domainkey." + domain)
|
txt, err := dkim.lookupTXT(selector + "._domainkey." + domain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.HasSuffix(err.Error(), "no such host") {
|
if strings.HasSuffix(err.Error(), "no such host") {
|
||||||
return nil, PERMFAIL, ErrVerifyNoKeyForSignature
|
return nil, ErrVerifyNoKeyForSignature
|
||||||
} else {
|
} else {
|
||||||
return nil, TEMPFAIL, ErrVerifyKeyUnavailable
|
return nil, ErrVerifyKeyUnavailable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// empty record
|
|
||||||
if len(txt) == 0 {
|
if len(txt) == 0 {
|
||||||
return nil, PERMFAIL, ErrVerifyNoKeyForSignature
|
return nil, ErrVerifyNoKeyForSignature
|
||||||
}
|
}
|
||||||
|
|
||||||
pkr := new(PubKey)
|
pkr := new(PubKey)
|
||||||
@@ -59,11 +57,11 @@ func PubKeyFromDns(selector, domain string) ([]*PubKey, verifyOutput, error) {
|
|||||||
case "v":
|
case "v":
|
||||||
// RFC: is this tag is specified it MUST be the first in the record
|
// RFC: is this tag is specified it MUST be the first in the record
|
||||||
if i != 0 {
|
if i != 0 {
|
||||||
return nil, PERMFAIL, ErrVerifyTagVMustBeTheFirst
|
return nil, ErrVerifyTagVMustBeTheFirst
|
||||||
}
|
}
|
||||||
pkr.Version = val
|
pkr.Version = val
|
||||||
if pkr.Version != "DKIM1" {
|
if pkr.Version != "DKIM1" {
|
||||||
return nil, PERMFAIL, ErrVerifyVersionMusBeDkim1
|
return nil, ErrVerifyVersionMusBeDkim1
|
||||||
}
|
}
|
||||||
case "h":
|
case "h":
|
||||||
p := strings.Split(strings.ToLower(val), ":")
|
p := strings.Split(strings.ToLower(val), ":")
|
||||||
@@ -80,18 +78,18 @@ func PubKeyFromDns(selector, domain string) ([]*PubKey, verifyOutput, error) {
|
|||||||
}
|
}
|
||||||
case "k":
|
case "k":
|
||||||
if strings.ToLower(val) != "rsa" {
|
if strings.ToLower(val) != "rsa" {
|
||||||
return nil, PERMFAIL, ErrVerifyBadKeyType
|
return nil, ErrVerifyBadKeyType
|
||||||
}
|
}
|
||||||
case "n":
|
case "n":
|
||||||
pkr.Note = val
|
pkr.Note = val
|
||||||
case "p":
|
case "p":
|
||||||
rawkey := val
|
rawkey := val
|
||||||
if rawkey == "" {
|
if rawkey == "" {
|
||||||
return nil, PERMFAIL, ErrVerifyRevokedKey
|
return nil, ErrVerifyRevokedKey
|
||||||
}
|
}
|
||||||
un64, err := base64.StdEncoding.DecodeString(rawkey)
|
un64, err := base64.StdEncoding.DecodeString(rawkey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, PERMFAIL, ErrVerifyBadKey
|
return nil, ErrVerifyBadKey
|
||||||
}
|
}
|
||||||
pk, err := x509.ParsePKIXPublicKey(un64)
|
pk, err := x509.ParsePKIXPublicKey(un64)
|
||||||
pkr.PubKey = *pk.(*rsa.PublicKey)
|
pkr.PubKey = *pk.(*rsa.PublicKey)
|
||||||
@@ -122,8 +120,8 @@ func PubKeyFromDns(selector, domain string) ([]*PubKey, verifyOutput, error) {
|
|||||||
|
|
||||||
// if no pubkey
|
// if no pubkey
|
||||||
if pkr.PubKey == (rsa.PublicKey{}) {
|
if pkr.PubKey == (rsa.PublicKey{}) {
|
||||||
return nil, PERMFAIL, ErrVerifyNoKey
|
return nil, ErrVerifyNoKey
|
||||||
}
|
}
|
||||||
|
|
||||||
return []*PubKey{pkr}, SUCCESS, nil
|
return []*PubKey{pkr}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user