You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564
  1. // Package dkim provides tools for signing and verify a email according to RFC 6376
  2. package dkim
  3. import (
  4. "bytes"
  5. "container/list"
  6. "crypto"
  7. "crypto/rand"
  8. "crypto/rsa"
  9. "crypto/sha1"
  10. "crypto/sha256"
  11. "crypto/x509"
  12. "encoding/base64"
  13. "encoding/pem"
  14. "hash"
  15. "regexp"
  16. "strings"
  17. "time"
  18. )
  19. const (
  20. CRLF = "\r\n"
  21. TAB = " "
  22. FWS = CRLF + TAB
  23. MaxHeaderLineLength = 70
  24. )
  25. type verifyOutput int
  26. const (
  27. SUCCESS verifyOutput = 1 + iota
  28. PERMFAIL
  29. TEMPFAIL
  30. NOTSIGNED
  31. TESTINGSUCCESS
  32. TESTINGPERMFAIL
  33. TESTINGTEMPFAIL
  34. )
  35. // sigOptions represents signing options
  36. type SigOptions struct {
  37. // DKIM version (default 1)
  38. Version uint
  39. // Private key used for signing (required)
  40. PrivateKey []byte
  41. // Domain (required)
  42. Domain string
  43. // Selector (required)
  44. Selector string
  45. // The Agent of User IDentifier
  46. Auid string
  47. // Message canonicalization (plain-text; OPTIONAL, default is
  48. // "simple/simple"). This tag informs the Verifier of the type of
  49. // canonicalization used to prepare the message for signing.
  50. Canonicalization string
  51. // The algorithm used to generate the signature
  52. //"rsa-sha1" or "rsa-sha256"
  53. Algo string
  54. // Signed header fields
  55. Headers []string
  56. // Body length count( if set to 0 this tag is ommited in Dkim header)
  57. BodyLength uint
  58. // Query Methods used to retrieve the public key
  59. QueryMethods []string
  60. // Add a signature timestamp
  61. AddSignatureTimestamp bool
  62. // Time validity of the signature (0=never)
  63. SignatureExpireIn uint64
  64. // CopiedHeaderFileds
  65. CopiedHeaderFields []string
  66. }
  67. // NewSigOptions returns new sigoption with some defaults value
  68. func NewSigOptions() SigOptions {
  69. return SigOptions{
  70. Version: 1,
  71. Canonicalization: "simple/simple",
  72. Algo: "rsa-sha256",
  73. Headers: []string{"from"},
  74. BodyLength: 0,
  75. QueryMethods: []string{"dns/txt"},
  76. AddSignatureTimestamp: true,
  77. SignatureExpireIn: 0,
  78. }
  79. }
  80. // Sign signs an email
  81. func Sign(email *[]byte, options SigOptions) error {
  82. var privateKey *rsa.PrivateKey
  83. var err error
  84. // PrivateKey
  85. if len(options.PrivateKey) == 0 {
  86. return ErrSignPrivateKeyRequired
  87. }
  88. d, _ := pem.Decode(options.PrivateKey)
  89. if d == nil {
  90. return ErrCandNotParsePrivateKey
  91. }
  92. // try to parse it as PKCS1 otherwise try PKCS8
  93. if key, err := x509.ParsePKCS1PrivateKey(d.Bytes); err != nil {
  94. if key, err := x509.ParsePKCS8PrivateKey(d.Bytes); err != nil {
  95. return ErrCandNotParsePrivateKey
  96. } else {
  97. privateKey = key.(*rsa.PrivateKey)
  98. }
  99. } else {
  100. privateKey = key
  101. }
  102. // Domain required
  103. if options.Domain == "" {
  104. return ErrSignDomainRequired
  105. }
  106. // Selector required
  107. if options.Selector == "" {
  108. return ErrSignSelectorRequired
  109. }
  110. // Canonicalization
  111. options.Canonicalization, err = validateCanonicalization(strings.ToLower(options.Canonicalization))
  112. if err != nil {
  113. return err
  114. }
  115. // Algo
  116. options.Algo = strings.ToLower(options.Algo)
  117. if options.Algo != "rsa-sha1" && options.Algo != "rsa-sha256" {
  118. return ErrSignBadAlgo
  119. }
  120. // Header must contain "from"
  121. hasFrom := false
  122. for i, h := range options.Headers {
  123. h = strings.ToLower(h)
  124. options.Headers[i] = h
  125. if h == "from" {
  126. hasFrom = true
  127. }
  128. }
  129. if !hasFrom {
  130. return ErrSignHeaderShouldContainsFrom
  131. }
  132. // Normalize
  133. headers, body, err := canonicalize(email, options.Canonicalization, options.Headers)
  134. if err != nil {
  135. return err
  136. }
  137. signHash := strings.Split(options.Algo, "-")
  138. // hash body
  139. bodyHash, err := getBodyHash(&body, signHash[1], options.BodyLength)
  140. if err != nil {
  141. return err
  142. }
  143. // Get dkim header base
  144. dkimHeader := newDkimHeaderBySigOptions(options)
  145. dHeader := dkimHeader.getHeaderBaseForSigning(bodyHash)
  146. canonicalizations := strings.Split(options.Canonicalization, "/")
  147. dHeaderCanonicalized, err := canonicalizeHeader(dHeader, canonicalizations[0])
  148. if err != nil {
  149. return err
  150. }
  151. headers = append(headers, []byte(dHeaderCanonicalized)...)
  152. headers = bytes.TrimRight(headers, " \r\n")
  153. // sign
  154. sig, err := getSignature(&headers, privateKey, signHash[1])
  155. // add to DKIM-Header
  156. subh := ""
  157. l := len(subh)
  158. for _, c := range sig {
  159. subh += string(c)
  160. l++
  161. if l >= MaxHeaderLineLength {
  162. dHeader += subh + FWS
  163. subh = ""
  164. l = 0
  165. }
  166. }
  167. dHeader += subh + CRLF
  168. *email = append([]byte(dHeader), *email...)
  169. return nil
  170. }
  171. // Verify verifies an email an return
  172. // state: SUCCESS or PERMFAIL or TEMPFAIL, TESTINGSUCCESS, TESTINGPERMFAIL
  173. // TESTINGTEMPFAIL or NOTSIGNED
  174. // error: if an error occurs during verification
  175. func Verify(email *[]byte, opts ...DNSOpt) (verifyOutput, error) {
  176. // parse email
  177. dkimHeader, err := GetHeader(email)
  178. if err != nil {
  179. if err == ErrDkimHeaderNotFound {
  180. return NOTSIGNED, ErrDkimHeaderNotFound
  181. }
  182. return PERMFAIL, err
  183. }
  184. // we do not set query method because if it's others, validation failed earlier
  185. pubKey, verifyOutputOnError, err := NewPubKeyRespFromDNS(dkimHeader.Selector, dkimHeader.Domain, opts...)
  186. if err != nil {
  187. // fix https://github.com/toorop/go-dkim/issues/1
  188. //return getVerifyOutput(verifyOutputOnError, err, pubKey.FlagTesting)
  189. return verifyOutputOnError, err
  190. }
  191. // Normalize
  192. headers, body, err := canonicalize(email, dkimHeader.MessageCanonicalization, dkimHeader.Headers)
  193. if err != nil {
  194. return getVerifyOutput(PERMFAIL, err, pubKey.FlagTesting)
  195. }
  196. sigHash := strings.Split(dkimHeader.Algorithm, "-")
  197. // check if hash algo are compatible
  198. compatible := false
  199. for _, algo := range pubKey.HashAlgo {
  200. if sigHash[1] == algo {
  201. compatible = true
  202. break
  203. }
  204. }
  205. if !compatible {
  206. return getVerifyOutput(PERMFAIL, ErrVerifyInappropriateHashAlgo, pubKey.FlagTesting)
  207. }
  208. // expired ?
  209. if !dkimHeader.SignatureExpiration.IsZero() && dkimHeader.SignatureExpiration.Second() < time.Now().Second() {
  210. return getVerifyOutput(PERMFAIL, ErrVerifySignatureHasExpired, pubKey.FlagTesting)
  211. }
  212. //println("|" + string(body) + "|")
  213. // get body hash
  214. bodyHash, err := getBodyHash(&body, sigHash[1], dkimHeader.BodyLength)
  215. if err != nil {
  216. return getVerifyOutput(PERMFAIL, err, pubKey.FlagTesting)
  217. }
  218. //println(bodyHash)
  219. if bodyHash != dkimHeader.BodyHash {
  220. return getVerifyOutput(PERMFAIL, ErrVerifyBodyHash, pubKey.FlagTesting)
  221. }
  222. // compute sig
  223. dkimHeaderCano, err := canonicalizeHeader(dkimHeader.rawForSign, strings.Split(dkimHeader.MessageCanonicalization, "/")[0])
  224. if err != nil {
  225. return getVerifyOutput(TEMPFAIL, err, pubKey.FlagTesting)
  226. }
  227. toSignStr := string(headers) + dkimHeaderCano
  228. toSign := bytes.TrimRight([]byte(toSignStr), " \r\n")
  229. err = verifySignature(toSign, dkimHeader.SignatureData, &pubKey.PubKey, sigHash[1])
  230. if err != nil {
  231. return getVerifyOutput(PERMFAIL, err, pubKey.FlagTesting)
  232. }
  233. return SUCCESS, nil
  234. }
  235. // getVerifyOutput returns output of verify fct according to the testing flag
  236. func getVerifyOutput(status verifyOutput, err error, flagTesting bool) (verifyOutput, error) {
  237. if !flagTesting {
  238. return status, err
  239. }
  240. switch status {
  241. case SUCCESS:
  242. return TESTINGSUCCESS, err
  243. case PERMFAIL:
  244. return TESTINGPERMFAIL, err
  245. case TEMPFAIL:
  246. return TESTINGTEMPFAIL, err
  247. }
  248. // should never happen but compilator sream whithout return
  249. return status, err
  250. }
  251. // canonicalize returns canonicalized version of header and body
  252. func canonicalize(email *[]byte, cano string, h []string) (headers, body []byte, err error) {
  253. body = []byte{}
  254. rxReduceWS := regexp.MustCompile(`[ \t]+`)
  255. rawHeaders, rawBody, err := getHeadersBody(email)
  256. if err != nil {
  257. return nil, nil, err
  258. }
  259. canonicalizations := strings.Split(cano, "/")
  260. // canonicalyze header
  261. headersList, err := getHeadersList(&rawHeaders)
  262. // pour chaque header a conserver on traverse tous les headers dispo
  263. // If multi instance of a field we must keep it from the bottom to the top
  264. var match *list.Element
  265. headersToKeepList := list.New()
  266. for _, headerToKeep := range h {
  267. match = nil
  268. headerToKeepToLower := strings.ToLower(headerToKeep)
  269. for e := headersList.Front(); e != nil; e = e.Next() {
  270. //fmt.Printf("|%s|\n", e.Value.(string))
  271. t := strings.Split(e.Value.(string), ":")
  272. if strings.ToLower(t[0]) == headerToKeepToLower {
  273. match = e
  274. }
  275. }
  276. if match != nil {
  277. headersToKeepList.PushBack(match.Value.(string) + "\r\n")
  278. headersList.Remove(match)
  279. }
  280. }
  281. //if canonicalizations[0] == "simple" {
  282. for e := headersToKeepList.Front(); e != nil; e = e.Next() {
  283. cHeader, err := canonicalizeHeader(e.Value.(string), canonicalizations[0])
  284. if err != nil {
  285. return headers, body, err
  286. }
  287. headers = append(headers, []byte(cHeader)...)
  288. }
  289. // canonicalyze body
  290. if canonicalizations[1] == "simple" {
  291. // simple
  292. // The "simple" body canonicalization algorithm ignores all empty lines
  293. // at the end of the message body. An empty line is a line of zero
  294. // length after removal of the line terminator. If there is no body or
  295. // no trailing CRLF on the message body, a CRLF is added. It makes no
  296. // other changes to the message body. In more formal terms, the
  297. // "simple" body canonicalization algorithm converts "*CRLF" at the end
  298. // of the body to a single "CRLF".
  299. // Note that a completely empty or missing body is canonicalized as a
  300. // single "CRLF"; that is, the canonicalized length will be 2 octets.
  301. body = bytes.TrimRight(rawBody, "\r\n")
  302. body = append(body, []byte{13, 10}...)
  303. } else {
  304. // relaxed
  305. // Ignore all whitespace at the end of lines. Implementations
  306. // MUST NOT remove the CRLF at the end of the line.
  307. // Reduce all sequences of WSP within a line to a single SP
  308. // character.
  309. // Ignore all empty lines at the end of the message body. "Empty
  310. // line" is defined in Section 3.4.3. If the body is non-empty but
  311. // does not end with a CRLF, a CRLF is added. (For email, this is
  312. // only possible when using extensions to SMTP or non-SMTP transport
  313. // mechanisms.)
  314. rawBody = rxReduceWS.ReplaceAll(rawBody, []byte(" "))
  315. for _, line := range bytes.SplitAfter(rawBody, []byte{10}) {
  316. line = bytes.TrimRight(line, " \r\n")
  317. body = append(body, line...)
  318. body = append(body, []byte{13, 10}...)
  319. }
  320. body = bytes.TrimRight(body, "\r\n")
  321. body = append(body, []byte{13, 10}...)
  322. }
  323. return
  324. }
  325. // canonicalizeHeader returns canonicalized version of header
  326. func canonicalizeHeader(header string, algo string) (string, error) {
  327. //rxReduceWS := regexp.MustCompile(`[ \t]+`)
  328. if algo == "simple" {
  329. // The "simple" header canonicalization algorithm does not change header
  330. // fields in any way. Header fields MUST be presented to the signing or
  331. // verification algorithm exactly as they are in the message being
  332. // signed or verified. In particular, header field names MUST NOT be
  333. // case folded and whitespace MUST NOT be changed.
  334. return header, nil
  335. } else if algo == "relaxed" {
  336. // The "relaxed" header canonicalization algorithm MUST apply the
  337. // following steps in order:
  338. // Convert all header field names (not the header field values) to
  339. // lowercase. For example, convert "SUBJect: AbC" to "subject: AbC".
  340. // Unfold all header field continuation lines as described in
  341. // [RFC5322]; in particular, lines with terminators embedded in
  342. // continued header field values (that is, CRLF sequences followed by
  343. // WSP) MUST be interpreted without the CRLF. Implementations MUST
  344. // NOT remove the CRLF at the end of the header field value.
  345. // Convert all sequences of one or more WSP characters to a single SP
  346. // character. WSP characters here include those before and after a
  347. // line folding boundary.
  348. // Delete all WSP characters at the end of each unfolded header field
  349. // value.
  350. // Delete any WSP characters remaining before and after the colon
  351. // separating the header field name from the header field value. The
  352. // colon separator MUST be retained.
  353. kv := strings.SplitN(header, ":", 2)
  354. if len(kv) != 2 {
  355. return header, ErrBadMailFormatHeaders
  356. }
  357. k := strings.ToLower(kv[0])
  358. k = strings.TrimSpace(k)
  359. v := removeFWS(kv[1])
  360. //v = rxReduceWS.ReplaceAllString(v, " ")
  361. //v = strings.TrimSpace(v)
  362. return k + ":" + v + CRLF, nil
  363. }
  364. return header, ErrSignBadCanonicalization
  365. }
  366. // getBodyHash return the hash (bas64encoded) of the body
  367. func getBodyHash(body *[]byte, algo string, bodyLength uint) (string, error) {
  368. var h hash.Hash
  369. if algo == "sha1" {
  370. h = sha1.New()
  371. } else {
  372. h = sha256.New()
  373. }
  374. toH := *body
  375. // if l tag (body length)
  376. if bodyLength != 0 {
  377. if uint(len(toH)) < bodyLength {
  378. return "", ErrBadDKimTagLBodyTooShort
  379. }
  380. toH = toH[0:bodyLength]
  381. }
  382. h.Write(toH)
  383. return base64.StdEncoding.EncodeToString(h.Sum(nil)), nil
  384. }
  385. // getSignature return signature of toSign using key
  386. func getSignature(toSign *[]byte, key *rsa.PrivateKey, algo string) (string, error) {
  387. var h1 hash.Hash
  388. var h2 crypto.Hash
  389. switch algo {
  390. case "sha1":
  391. h1 = sha1.New()
  392. h2 = crypto.SHA1
  393. break
  394. case "sha256":
  395. h1 = sha256.New()
  396. h2 = crypto.SHA256
  397. break
  398. default:
  399. return "", ErrVerifyInappropriateHashAlgo
  400. }
  401. // sign
  402. h1.Write(*toSign)
  403. sig, err := rsa.SignPKCS1v15(rand.Reader, key, h2, h1.Sum(nil))
  404. if err != nil {
  405. return "", err
  406. }
  407. return base64.StdEncoding.EncodeToString(sig), nil
  408. }
  409. // verifySignature verify signature from pubkey
  410. func verifySignature(toSign []byte, sig64 string, key *rsa.PublicKey, algo string) error {
  411. var h1 hash.Hash
  412. var h2 crypto.Hash
  413. switch algo {
  414. case "sha1":
  415. h1 = sha1.New()
  416. h2 = crypto.SHA1
  417. break
  418. case "sha256":
  419. h1 = sha256.New()
  420. h2 = crypto.SHA256
  421. break
  422. default:
  423. return ErrVerifyInappropriateHashAlgo
  424. }
  425. h1.Write(toSign)
  426. sig, err := base64.StdEncoding.DecodeString(sig64)
  427. if err != nil {
  428. return err
  429. }
  430. return rsa.VerifyPKCS1v15(key, h2, h1.Sum(nil), sig)
  431. }
  432. // removeFWS removes all FWS from string
  433. func removeFWS(in string) string {
  434. rxReduceWS := regexp.MustCompile(`[ \t]+`)
  435. out := strings.Replace(in, "\n", "", -1)
  436. out = strings.Replace(out, "\r", "", -1)
  437. out = rxReduceWS.ReplaceAllString(out, " ")
  438. return strings.TrimSpace(out)
  439. }
  440. // validateCanonicalization validate canonicalization (c flag)
  441. func validateCanonicalization(cano string) (string, error) {
  442. p := strings.Split(cano, "/")
  443. if len(p) > 2 {
  444. return "", ErrSignBadCanonicalization
  445. }
  446. if len(p) == 1 {
  447. cano = cano + "/simple"
  448. }
  449. for _, c := range p {
  450. if c != "simple" && c != "relaxed" {
  451. return "", ErrSignBadCanonicalization
  452. }
  453. }
  454. return cano, nil
  455. }
  456. // getHeadersList returns headers as list
  457. func getHeadersList(rawHeader *[]byte) (*list.List, error) {
  458. headersList := list.New()
  459. currentHeader := []byte{}
  460. for _, line := range bytes.SplitAfter(*rawHeader, []byte{10}) {
  461. if line[0] == 32 || line[0] == 9 {
  462. if len(currentHeader) == 0 {
  463. return headersList, ErrBadMailFormatHeaders
  464. }
  465. currentHeader = append(currentHeader, line...)
  466. } else {
  467. // New header, save current if exists
  468. if len(currentHeader) != 0 {
  469. headersList.PushBack(string(bytes.TrimRight(currentHeader, "\r\n")))
  470. currentHeader = []byte{}
  471. }
  472. currentHeader = append(currentHeader, line...)
  473. }
  474. }
  475. headersList.PushBack(string(currentHeader))
  476. return headersList, nil
  477. }
  478. // getHeadersBody return headers and body
  479. func getHeadersBody(email *[]byte) ([]byte, []byte, error) {
  480. substitutedEmail := *email
  481. // only replace \n with \r\n when \r\n\r\n not exists
  482. if bytes.Index(*email, []byte{13, 10, 13, 10}) < 0 {
  483. // \n -> \r\n
  484. substitutedEmail = bytes.Replace(*email, []byte{10}, []byte{13, 10}, -1)
  485. }
  486. parts := bytes.SplitN(substitutedEmail, []byte{13, 10, 13, 10}, 2)
  487. if len(parts) != 2 {
  488. return []byte{}, []byte{}, ErrBadMailFormat
  489. }
  490. // Empty body
  491. if len(parts[1]) == 0 {
  492. parts[1] = []byte{13, 10}
  493. }
  494. return parts[0], parts[1], nil
  495. }