diff --git a/cmd/dkim-keygen/main.go b/cmd/dkim-keygen/main.go index f5731bf..208b162 100644 --- a/cmd/dkim-keygen/main.go +++ b/cmd/dkim-keygen/main.go @@ -73,7 +73,16 @@ func main() { var pubBytes []byte switch pubKey := privKey.Public().(type) { case *rsa.PublicKey: - pubBytes = x509.MarshalPKCS1PublicKey(pubKey) + // RFC 6376 is inconsistent about whether RSA public keys should + // be formatted as RSAPublicKey or SubjectPublicKeyInfo. + // Erratum 3017 (https://www.rfc-editor.org/errata/eid3017) + // proposes allowing both. We use SubjectPublicKeyInfo for + // consistency with other implementations including opendkim, + // Gmail, and Fastmail. + pubBytes, err = x509.MarshalPKIXPublicKey(pubKey) + if err != nil { + log.Fatalf("Failed to marshal public key: %v", err) + } case ed25519.PublicKey: pubBytes = pubKey default: diff --git a/dkim/query.go b/dkim/query.go index 8fd6ecb..dde15c2 100644 --- a/dkim/query.go +++ b/dkim/query.go @@ -118,7 +118,14 @@ func parsePublicKey(s string) (*queryResult, error) { case "rsa", "": pub, err := x509.ParsePKIXPublicKey(b) if err != nil { - return nil, permFailError("key syntax error: " + err.Error()) + // RFC 6376 is inconsistent about whether RSA public keys should + // be formatted as RSAPublicKey or SubjectPublicKeyInfo. + // Erratum 3017 (https://www.rfc-editor.org/errata/eid3017) proposes + // allowing both. + pub, err = x509.ParsePKCS1PublicKey(b) + if err != nil { + return nil, permFailError("key syntax error: " + err.Error()) + } } rsaPub, ok := pub.(*rsa.PublicKey) if !ok { diff --git a/dkim/query_test.go b/dkim/query_test.go index 6e86de4..d49ae2f 100644 --- a/dkim/query_test.go +++ b/dkim/query_test.go @@ -4,6 +4,12 @@ import ( "fmt" ) +const dnsRawRSAPublicKey = "v=DKIM1; p=MIGJAoGBALVI635dLK4cJJAH3Lx6upo3X/L" + + "m1tQz3mezcWTA3BUBnyIsdnRf57aD5BtNmhPrYYDlWlzw3" + + "UgnKisIxktkk5+iMQMlFtAS10JB8L3YadXNJY+JBcbeSi5" + + "TgJe4WFzNgW95FWDAuSTRXSWZfA/8xjflbTLDx0euFZOM7" + + "C4T0GwLAgMBAAE=" + const dnsPublicKey = "v=DKIM1; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQ" + "KBgQDwIRP/UC3SBsEmGqZ9ZJW3/DkMoGeLnQg1fWn7/zYt" + "IxN2SnFCjxOCKG9v3b4jYfcTNh5ijSsq631uBItLa7od+v" + @@ -21,6 +27,8 @@ func queryTest(domain, selector string, txtLookup txtLookupFunc) (*queryResult, switch record { case "brisbane._domainkey.example.com", "brisbane._domainkey.example.org", "test._domainkey.football.example.com": return parsePublicKey(dnsPublicKey) + case "newengland._domainkey.example.com": + return parsePublicKey(dnsRawRSAPublicKey) case "brisbane._domainkey.football.example.com": return parsePublicKey(dnsEd25519PublicKey) } diff --git a/dkim/verify_test.go b/dkim/verify_test.go index 228eff6..dfa7092 100644 --- a/dkim/verify_test.go +++ b/dkim/verify_test.go @@ -114,6 +114,52 @@ func TestVerifyWithOption(t *testing.T) { } } +const verifiedRawRSAMailString = `DKIM-Signature: a=rsa-sha256; bh=2jUSOH9NhtVGCQWNr9BrIAPreKQjO6Sn7XIkfJVOzv8=; + c=simple/simple; d=example.com; + h=Received:From:To:Subject:Date:Message-ID; i=joe@football.example.com; + s=newengland; t=1615825284; v=1; + b=Xh4Ujb2wv5x54gXtulCiy4C0e+plRm6pZ4owF+kICpYzs/8WkTVIDBrzhJP0DAYCpnL62T0G + k+0OH8pi/yqETVjKtKk+peMnNvKkut0GeWZMTze0bfq3/JUK3Ln3jTzzpXxrgVnvBxeY9EZIL4g + s4wwFRRKz/1bksZGSjD8uuSU= +Received: from client1.football.example.com [192.0.2.1] + by submitserver.example.com with SUBMISSION; + Fri, 11 Jul 2003 21:01:54 -0700 (PDT) +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 testRawRSAVerification = &Verification{ + Domain: "example.com", + Identifier: "joe@football.example.com", + HeaderKeys: []string{"Received", "From", "To", "Subject", "Date", "Message-ID"}, + Time: time.Unix(1615825284, 0), +} + +func TestVerify_rawRSA(t *testing.T) { + r := newMailStringReader(verifiedRawRSAMailString) + + verifications, err := Verify(r) + if err != nil { + t.Fatalf("Expected no error while verifying signature, got: %v", err) + } else if len(verifications) != 1 { + t.Fatalf("Expected exactly one verification, got %v", len(verifications)) + } + + v := verifications[0] + if !reflect.DeepEqual(testRawRSAVerification, v) { + t.Errorf("Expected verification to be \n%+v\n but got \n%+v", testRawRSAVerification, 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 :