選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

dkim.go 15KB

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