From a5db695813cbb56aa1ad0e15f46981c8845ff084 Mon Sep 17 00:00:00 2001 From: emersion Date: Tue, 12 Mar 2019 01:11:51 +0100 Subject: [PATCH] Add Verify tests for Ed25519 --- dkim_test.go | 15 +++++++++++-- query_test.go | 15 ++++++++++++- verify.go | 27 +++++++++++++---------- verify_test.go | 59 +++++++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 100 insertions(+), 16 deletions(-) diff --git a/dkim_test.go b/dkim_test.go index 4c4a95e..5d5311c 100644 --- a/dkim_test.go +++ b/dkim_test.go @@ -3,8 +3,11 @@ package dkim import ( "crypto/rsa" "crypto/x509" + "encoding/base64" "encoding/pem" "time" + + "golang.org/x/crypto/ed25519" ) const testPrivateKeyPEM = `-----BEGIN RSA PRIVATE KEY----- @@ -24,17 +27,25 @@ GMot/L2x0IYyMLAz6oLWh2hm7zwtb0CgOrPo1ke44hFYnfc= -----END RSA PRIVATE KEY----- ` +const testEd25519PrivateKeyBase64 = "nWGxne/9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A=" + var ( testPrivateKey *rsa.PrivateKey + testEd25519PrivateKey ed25519.PrivateKey ) func init() { block, _ := pem.Decode([]byte(testPrivateKeyPEM)) - key, err := x509.ParsePKCS1PrivateKey(block.Bytes) + var err error + testPrivateKey, err = x509.ParsePKCS1PrivateKey(block.Bytes) + if err != nil { + panic(err) + } + + testEd25519PrivateKey, err = base64.StdEncoding.DecodeString(testEd25519PrivateKeyBase64) if err != nil { panic(err) } - testPrivateKey = key now = func() time.Time { return time.Unix(424242, 0) diff --git a/query_test.go b/query_test.go index 2d01afb..d17860f 100644 --- a/query_test.go +++ b/query_test.go @@ -1,15 +1,28 @@ package dkim +import ( + "fmt" +) + const dnsPublicKey = "v=DKIM1; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQ" + "KBgQDwIRP/UC3SBsEmGqZ9ZJW3/DkMoGeLnQg1fWn7/zYt" + "IxN2SnFCjxOCKG9v3b4jYfcTNh5ijSsq631uBItLa7od+v" + "/RtdC2UzJ1lWT947qR+Rcac2gbto/NMqJ0fzfVjH4OuKhi" + "tdY9tf6mcwGjaNBcWToIMmPSPDdQPNUYckcQ2QIDAQAB" +const dnsEd25519PublicKey = "v=DKIM1; k=ed25519; p=11qYAYKxCrfVS/7TyWQHOg7hcvPapiMlrwIaaPcHURo=" + func init() { queryMethods["dns/txt"] = queryTest } func queryTest(domain, selector string) (*queryResult, error) { - return parsePublicKey(dnsPublicKey) + record := selector + "._domainkey." + domain + switch record { + case "brisbane._domainkey.example.com", "brisbane._domainkey.example.org", "test._domainkey.football.example.com": + return parsePublicKey(dnsPublicKey) + case "brisbane._domainkey.football.example.com": + return parsePublicKey(dnsEd25519PublicKey) + } + return nil, fmt.Errorf("unknown test DNS record %v", record) } diff --git a/verify.go b/verify.go index 181b716..56cab01 100644 --- a/verify.go +++ b/verify.go @@ -153,7 +153,7 @@ func verify(h header, r io.Reader, sigField, sigValue string) (*Verification, er return verif, permFailError("incompatible signature version") } - verif.Domain = params["d"] + verif.Domain = stripWhitespace(params["d"]) for _, tag := range requiredTags { if _, ok := params[tag]; !ok { @@ -162,13 +162,13 @@ func verify(h header, r io.Reader, sigField, sigValue string) (*Verification, er } if i, ok := params["i"]; ok { - if !strings.HasSuffix(i, "@"+params["d"]) && !strings.HasSuffix(i, "."+params["d"]) { + verif.Identifier = stripWhitespace(i) + if !strings.HasSuffix(verif.Identifier, "@"+verif.Domain) && !strings.HasSuffix(verif.Identifier, "."+verif.Domain) { return verif, permFailError("domain mismatch") } } else { - params["i"] = "@" + params["d"] + verif.Identifier = "@" + verif.Domain } - verif.Identifier = params["i"] headerKeys := parseTagList(params["h"]) ok := false @@ -210,7 +210,7 @@ func verify(h header, r io.Reader, sigField, sigValue string) (*Verification, er var res *queryResult for _, method := range methods { if query, ok := queryMethods[QueryMethod(method)]; ok { - res, err = query(params["d"], params["s"]) + res, err = query(verif.Domain, stripWhitespace(params["s"])) break } } @@ -221,7 +221,7 @@ func verify(h header, r io.Reader, sigField, sigValue string) (*Verification, er } // Parse algos - algos := strings.SplitN(params["a"], "-", 2) + algos := strings.SplitN(stripWhitespace(params["a"]), "-", 2) if len(algos) != 2 { return verif, permFailError("malformed algorithm name") } @@ -281,7 +281,7 @@ func verify(h header, r io.Reader, sigField, sigValue string) (*Verification, er var bodyLen int64 = -1 if lenStr, ok := params["l"]; ok { - l, err := strconv.ParseInt(lenStr, 10, 64) + l, err := strconv.ParseInt(stripWhitespace(lenStr), 10, 64) if err != nil { return verif, permFailError("malformed body length: " + err.Error()) } else if l < 0 { @@ -351,7 +351,7 @@ func verify(h header, r io.Reader, sigField, sigValue string) (*Verification, er func parseTagList(s string) []string { tags := strings.Split(s, ":") for i, t := range tags { - tags[i] = strings.TrimSpace(t) + tags[i] = stripWhitespace(t) } return tags } @@ -360,7 +360,7 @@ func parseCanonicalization(s string) (headerCan, bodyCan Canonicalization) { headerCan = CanonicalizationSimple bodyCan = CanonicalizationSimple - cans := strings.SplitN(s, "/", 2) + cans := strings.SplitN(stripWhitespace(s), "/", 2) if cans[0] != "" { headerCan = Canonicalization(cans[0]) } @@ -371,7 +371,7 @@ func parseCanonicalization(s string) (headerCan, bodyCan Canonicalization) { } func parseTime(s string) (time.Time, error) { - sec, err := strconv.ParseInt(s, 10, 64) + sec, err := strconv.ParseInt(stripWhitespace(s), 10, 64) if err != nil { return time.Time{}, err } @@ -379,13 +379,16 @@ func parseTime(s string) (time.Time, error) { } func decodeBase64String(s string) ([]byte, error) { - s = strings.Map(func(r rune) rune { + return base64.StdEncoding.DecodeString(stripWhitespace(s)) +} + +func stripWhitespace(s string) string { + return strings.Map(func(r rune) rune { if unicode.IsSpace(r) { return -1 } return r }, s) - return base64.StdEncoding.DecodeString(s) } func removeSignature(s string) string { diff --git a/verify_test.go b/verify_test.go index 503add2..6e91782 100644 --- a/verify_test.go +++ b/verify_test.go @@ -1,9 +1,11 @@ package dkim import ( + "io" "reflect" "strings" "testing" + "time" ) const verifiedMailString = `DKIM-Signature: v=1; a=rsa-sha256; s=brisbane; d=example.com; @@ -37,8 +39,12 @@ var testVerification = &Verification{ BodyLength: -1, } +func newMailStringReader(s string) io.Reader { + return strings.NewReader(strings.Replace(s, "\n", "\r\n", -1)) +} + func TestVerify(t *testing.T) { - r := strings.NewReader(strings.Replace(verifiedMailString, "\n", "\r\n", -1)) + r := newMailStringReader(verifiedMailString) verifications, err := Verify(r) if err != nil { @@ -52,3 +58,54 @@ func TestVerify(t *testing.T) { t.Errorf("Expected verification to be \n%+v\n but got \n%+v", testVerification, v) } } + +const verifiedEd25519MailString = `DKIM-Signature: v=1; a=ed25519-sha256; c=relaxed/relaxed; + d=football.example.com; i=@football.example.com; + q=dns/txt; s=brisbane; t=1528637909; h=from : to : + subject : date : message-id : from : subject : date; + bh=2jUSOH9NhtVGCQWNr9BrIAPreKQjO6Sn7XIkfJVOzv8=; + b=/gCrinpcQOoIfuHNQIbq4pgh9kyIK3AQUdt9OdqQehSwhEIug4D11Bus + Fa3bT3FY5OsU7ZbnKELq+eXdp1Q1Dw== +DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; + d=football.example.com; i=@football.example.com; + q=dns/txt; s=test; t=1528637909; h=from : to : subject : + date : message-id : from : subject : date; + bh=2jUSOH9NhtVGCQWNr9BrIAPreKQjO6Sn7XIkfJVOzv8=; + b=F45dVWDfMbQDGHJFlXUNB2HKfbCeLRyhDXgFpEL8GwpsRe0IeIixNTe3 + DhCVlUrSjV4BwcVcOF6+FF3Zo9Rpo1tFOeS9mPYQTnGdaSGsgeefOsk2Jz + dA+L10TeYt9BgDfQNZtKdN1WO//KgIqXP7OdEFE4LjFYNcUxZQ4FADY+8= +From: Joe SixPack +To: Suzie Q +Subject: Is dinner ready? +Date: Fri, 11 Jul 2003 21:00:37 -0700 (PDT) +Message-ID: <20030712040037.46341.5F8J@football.example.com> + +Hi. + +We lost the game. Are you hungry yet? + +Joe.` + +var testEd25519Verification = &Verification{ + Domain: "football.example.com", + Identifier: "@football.example.com", + HeaderKeys: []string{"from", "to", "subject", "date", "message-id", "from", "subject", "date"}, + BodyLength: -1, + Time: time.Unix(1528637909, 0), +} + +func TestVerify_ed25519(t *testing.T) { + r := newMailStringReader(verifiedEd25519MailString) + + verifications, err := Verify(r) + if err != nil { + t.Fatalf("Expected no error while verifying signature, got: %v", err) + } else if len(verifications) != 2 { + t.Fatalf("Expected exactly two verifications, got %v", len(verifications)) + } + + v := verifications[0] + if !reflect.DeepEqual(testEd25519Verification, v) { + t.Errorf("Expected verification to be \n%+v\n but got \n%+v", testEd25519Verification, v) + } +}