Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

dkimHeader.go 18KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545
  1. package dkim
  2. import (
  3. "bytes"
  4. "fmt"
  5. "net/mail"
  6. "net/textproto"
  7. "strconv"
  8. "strings"
  9. "time"
  10. )
  11. type DKIMHeader struct {
  12. // Version This tag defines the version of DKIM
  13. // specification that applies to the signature record.
  14. // tag v
  15. Version string
  16. // The algorithm used to generate the signature..
  17. // Verifiers MUST support "rsa-sha1" and "rsa-sha256";
  18. // Signers SHOULD sign using "rsa-sha256".
  19. // tag a
  20. Algorithm string
  21. // The signature data (base64).
  22. // Whitespace is ignored in this value and MUST be
  23. // ignored when reassembling the original signature.
  24. // In particular, the signing process can safely insert
  25. // FWS in this value in arbitrary places to conform to line-length
  26. // limits.
  27. // tag b
  28. SignatureData string
  29. // The hash of the canonicalized body part of the message as
  30. // limited by the "l=" tag (base64; REQUIRED).
  31. // Whitespace is ignored in this value and MUST be ignored when reassembling the original
  32. // signature. In particular, the signing process can safely insert
  33. // FWS in this value in arbitrary places to conform to line-length
  34. // limits.
  35. // tag bh
  36. BodyHash string
  37. // Message canonicalization (plain-text; OPTIONAL, default is
  38. //"simple/simple"). This tag informs the Verifier of the type of
  39. // canonicalization used to prepare the message for signing. It
  40. // consists of two names separated by a "slash" (%d47) character,
  41. // corresponding to the header and body canonicalization algorithms,
  42. // respectively. These algorithms are described in Section 3.4. If
  43. // only one algorithm is named, that algorithm is used for the header
  44. // and "simple" is used for the body. For example, "c=relaxed" is
  45. // treated the same as "c=relaxed/simple".
  46. // tag c
  47. MessageCanonicalization string
  48. // The SDID claiming responsibility for an introduction of a message
  49. // into the mail stream (plain-text; REQUIRED). Hence, the SDID
  50. // value is used to form the query for the public key. The SDID MUST
  51. // correspond to a valid DNS name under which the DKIM key record is
  52. // published. The conventions and semantics used by a Signer to
  53. // create and use a specific SDID are outside the scope of this
  54. // specification, as is any use of those conventions and semantics.
  55. // When presented with a signature that does not meet these
  56. // requirements, Verifiers MUST consider the signature invalid.
  57. // Internationalized domain names MUST be encoded as A-labels, as
  58. // described in Section 2.3 of [RFC5890].
  59. // tag d
  60. Domain string
  61. // Signed header fields (plain-text, but see description; REQUIRED).
  62. // A colon-separated list of header field names that identify the
  63. // header fields presented to the signing algorithm. The field MUST
  64. // contain the complete list of header fields in the order presented
  65. // to the signing algorithm. The field MAY contain names of header
  66. // fields that do not exist when signed; nonexistent header fields do
  67. // not contribute to the signature computation (that is, they are
  68. // treated as the null input, including the header field name, the
  69. // separating colon, the header field value, and any CRLF
  70. // terminator). The field MAY contain multiple instances of a header
  71. // field name, meaning multiple occurrences of the corresponding
  72. // header field are included in the header hash. The field MUST NOT
  73. // include the DKIM-Signature header field that is being created or
  74. // verified but may include others. Folding whitespace (FWS) MAY be
  75. // included on either side of the colon separator. Header field
  76. // names MUST be compared against actual header field names in a
  77. // case-insensitive manner. This list MUST NOT be empty. See
  78. // Section 5.4 for a discussion of choosing header fields to sign and
  79. // Section 5.4.2 for requirements when signing multiple instances of
  80. // a single field.
  81. // tag h
  82. Headers []string
  83. // The Agent or User Identifier (AUID) on behalf of which the SDID is
  84. // taking responsibility (dkim-quoted-printable; OPTIONAL, default is
  85. // an empty local-part followed by an "@" followed by the domain from
  86. // the "d=" tag).
  87. // The syntax is a standard email address where the local-part MAY be
  88. // omitted. The domain part of the address MUST be the same as, or a
  89. // subdomain of, the value of the "d=" tag.
  90. // Internationalized domain names MUST be encoded as A-labels, as
  91. // described in Section 2.3 of [RFC5890].
  92. // tag i
  93. Auid string
  94. // Body length count (plain-text unsigned decimal integer; OPTIONAL,
  95. // default is entire body). This tag informs the Verifier of the
  96. // number of octets in the body of the email after canonicalization
  97. // included in the cryptographic hash, starting from 0 immediately
  98. // following the CRLF preceding the body. This value MUST NOT be
  99. // larger than the actual number of octets in the canonicalized
  100. // message body. See further discussion in Section 8.2.
  101. // tag l
  102. BodyLength uint
  103. // A colon-separated list of query methods used to retrieve the
  104. // public key (plain-text; OPTIONAL, default is "dns/txt"). Each
  105. // query method is of the form "type[/options]", where the syntax and
  106. // semantics of the options depend on the type and specified options.
  107. // If there are multiple query mechanisms listed, the choice of query
  108. // mechanism MUST NOT change the interpretation of the signature.
  109. // Implementations MUST use the recognized query mechanisms in the
  110. // order presented. Unrecognized query mechanisms MUST be ignored.
  111. // Currently, the only valid value is "dns/txt", which defines the
  112. // DNS TXT resource record (RR) lookup algorithm described elsewhere
  113. // in this document. The only option defined for the "dns" query
  114. // type is "txt", which MUST be included. Verifiers and Signers MUST
  115. // support "dns/txt".
  116. // tag q
  117. QueryMethods []string
  118. // The selector subdividing the namespace for the "d=" (domain) tag
  119. // (plain-text; REQUIRED).
  120. // Internationalized selector names MUST be encoded as A-labels, as
  121. // described in Section 2.3 of [RFC5890].
  122. // tag s
  123. Selector string
  124. // Signature Timestamp (plain-text unsigned decimal integer;
  125. // RECOMMENDED, default is an unknown creation time). The time that
  126. // this signature was created. The format is the number of seconds
  127. // since 00:00:00 on January 1, 1970 in the UTC time zone. The value
  128. // is expressed as an unsigned integer in decimal ASCII. This value
  129. // is not constrained to fit into a 31- or 32-bit integer.
  130. // Implementations SHOULD be prepared to handle values up to at least
  131. // 10^12 (until approximately AD 200,000; this fits into 40 bits).
  132. // To avoid denial-of-service attacks, implementations MAY consider
  133. // any value longer than 12 digits to be infinite. Leap seconds are
  134. // not counted. Implementations MAY ignore signatures that have a
  135. // timestamp in the future.
  136. // tag t
  137. SignatureTimestamp time.Time
  138. // Signature Expiration (plain-text unsigned decimal integer;
  139. // RECOMMENDED, default is no expiration). The format is the same as
  140. // in the "t=" tag, represented as an absolute date, not as a time
  141. // delta from the signing timestamp. The value is expressed as an
  142. // unsigned integer in decimal ASCII, with the same constraints on
  143. // the value in the "t=" tag. Signatures MAY be considered invalid
  144. // if the verification time at the Verifier is past the expiration
  145. // date. The verification time should be the time that the message
  146. // was first received at the administrative domain of the Verifier if
  147. // that time is reliably available; otherwise, the current time
  148. // should be used. The value of the "x=" tag MUST be greater than
  149. // the value of the "t=" tag if both are present.
  150. //tag x
  151. SignatureExpiration time.Time
  152. // Copied header fields (dkim-quoted-printable, but see description;
  153. // OPTIONAL, default is null). A vertical-bar-separated list of
  154. // selected header fields present when the message was signed,
  155. // including both the field name and value. It is not required to
  156. // include all header fields present at the time of signing. This
  157. // field need not contain the same header fields listed in the "h="
  158. // tag. The header field text itself must encode the vertical bar
  159. // ("|", %x7C) character (i.e., vertical bars in the "z=" text are
  160. // meta-characters, and any actual vertical bar characters in a
  161. // copied header field must be encoded). Note that all whitespace
  162. // must be encoded, including whitespace between the colon and the
  163. // header field value. After encoding, FWS MAY be added at arbitrary
  164. // locations in order to avoid excessively long lines; such
  165. // whitespace is NOT part of the value of the header field and MUST
  166. // be removed before decoding.
  167. // The header fields referenced by the "h=" tag refer to the fields
  168. // in the [RFC5322] header of the message, not to any copied fields
  169. // in the "z=" tag. Copied header field values are for diagnostic
  170. // use.
  171. // tag z
  172. CopiedHeaderFields []string
  173. // HeaderMailFromDomain store the raw email address of the header Mail From
  174. // used for verifying in case of multiple DKIM header (we will prioritise
  175. // header with d = mail from domain)
  176. //HeaderMailFromDomain string
  177. // RawForsign represents the raw part (without canonicalization) of the header
  178. // used for computint sig in verify process
  179. rawForSign string
  180. }
  181. // NewDkimHeaderBySigOptions return a new DkimHeader initioalized with sigOptions value
  182. func newDkimHeaderBySigOptions(options SigOptions) *DKIMHeader {
  183. h := new(DKIMHeader)
  184. h.Version = "1"
  185. h.Algorithm = options.Algo
  186. h.MessageCanonicalization = options.Canonicalization
  187. h.Domain = options.Domain
  188. h.Headers = options.Headers
  189. h.Auid = options.Auid
  190. h.BodyLength = options.BodyLength
  191. h.QueryMethods = options.QueryMethods
  192. h.Selector = options.Selector
  193. if options.AddSignatureTimestamp {
  194. h.SignatureTimestamp = time.Now()
  195. }
  196. if options.SignatureExpireIn > 0 {
  197. h.SignatureExpiration = time.Now().Add(time.Duration(options.SignatureExpireIn) * time.Second)
  198. }
  199. h.CopiedHeaderFields = options.CopiedHeaderFields
  200. return h
  201. }
  202. // GetHeader return a new DKIMHeader by parsing an email
  203. // Note: according to RFC 6376 an email can have multiple DKIM Header
  204. // in this case we return the last inserted or the last with d== mail from
  205. func GetHeader(email *[]byte) (*DKIMHeader, error) {
  206. m, err := mail.ReadMessage(bytes.NewReader(*email))
  207. if err != nil {
  208. return nil, err
  209. }
  210. // DKIM header ?
  211. if len(m.Header[textproto.CanonicalMIMEHeaderKey("DKIM-Signature")]) == 0 {
  212. return nil, ErrDkimHeaderNotFound
  213. }
  214. // Get mail from domain
  215. mailFromDomain := ""
  216. mailfrom, err := mail.ParseAddress(m.Header.Get(textproto.CanonicalMIMEHeaderKey("From")))
  217. if err != nil {
  218. if err.Error() != "mail: no address" {
  219. return nil, err
  220. }
  221. } else {
  222. t := strings.SplitAfter(mailfrom.Address, "@")
  223. if len(t) > 1 {
  224. mailFromDomain = strings.ToLower(t[1])
  225. }
  226. }
  227. // get raw dkim header
  228. // we can't use m.header because header key will be converted with textproto.CanonicalMIMEHeaderKey
  229. // ie if key in header is not DKIM-Signature but Dkim-Signature or DKIM-signature ot... other
  230. // combination of case, verify will fail.
  231. rawHeaders, _, err := getHeadersBody(email)
  232. if err != nil {
  233. return nil, ErrBadMailFormat
  234. }
  235. rawHeadersList, err := getHeadersList(&rawHeaders)
  236. if err != nil {
  237. return nil, err
  238. }
  239. dkHeaders := []string{}
  240. for h := rawHeadersList.Front(); h != nil; h = h.Next() {
  241. if strings.HasPrefix(strings.ToLower(h.Value.(string)), "dkim-signature") {
  242. dkHeaders = append(dkHeaders, h.Value.(string))
  243. }
  244. }
  245. var keep *DKIMHeader
  246. var keepErr error
  247. //for _, dk := range m.Header[textproto.CanonicalMIMEHeaderKey("DKIM-Signature")] {
  248. for _, h := range dkHeaders {
  249. parsed, err := parseDkHeader(h)
  250. // if malformed dkim header try next
  251. if err != nil {
  252. keepErr = err
  253. continue
  254. }
  255. // Keep first dkim headers
  256. if keep == nil {
  257. keep = parsed
  258. }
  259. // if d flag == domain keep this header and return
  260. if mailFromDomain == parsed.Domain {
  261. return parsed, nil
  262. }
  263. }
  264. if keep == nil {
  265. return nil, keepErr
  266. }
  267. return keep, nil
  268. }
  269. // parseDkHeader parse raw dkim header
  270. func parseDkHeader(header string) (dkh *DKIMHeader, err error) {
  271. dkh = new(DKIMHeader)
  272. keyVal := strings.SplitN(header, ":", 2)
  273. t := strings.LastIndex(header, "b=")
  274. if t == -1 {
  275. return nil, ErrDkimHeaderBTagNotFound
  276. }
  277. dkh.rawForSign = header[0 : t+2]
  278. p := strings.IndexByte(header[t:], ';')
  279. if p != -1 {
  280. dkh.rawForSign = dkh.rawForSign + header[t+p:]
  281. }
  282. // Mandatory
  283. mandatoryFlags := make(map[string]bool, 7) //(b'v', b'a', b'b', b'bh', b'd', b'h', b's')
  284. mandatoryFlags["v"] = false
  285. mandatoryFlags["a"] = false
  286. mandatoryFlags["b"] = false
  287. mandatoryFlags["bh"] = false
  288. mandatoryFlags["d"] = false
  289. mandatoryFlags["h"] = false
  290. mandatoryFlags["s"] = false
  291. // default values
  292. dkh.MessageCanonicalization = "simple/simple"
  293. dkh.QueryMethods = []string{"dns/txt"}
  294. // unfold && clean
  295. val := removeFWS(keyVal[1])
  296. val = strings.Replace(val, " ", "", -1)
  297. fs := strings.Split(val, ";")
  298. for _, f := range fs {
  299. if f == "" {
  300. continue
  301. }
  302. flagData := strings.SplitN(f, "=", 2)
  303. // https://github.com/toorop/go-dkim/issues/2
  304. // if flag is not in the form key=value (eg doesn't have "=")
  305. if len(flagData) != 2 {
  306. return nil, ErrDkimHeaderBadFormat
  307. }
  308. flag := strings.ToLower(strings.TrimSpace(flagData[0]))
  309. data := strings.TrimSpace(flagData[1])
  310. switch flag {
  311. case "v":
  312. if data != "1" {
  313. return nil, ErrDkimVersionNotsupported
  314. }
  315. dkh.Version = data
  316. mandatoryFlags["v"] = true
  317. case "a":
  318. dkh.Algorithm = strings.ToLower(data)
  319. if dkh.Algorithm != "rsa-sha1" && dkh.Algorithm != "rsa-sha256" {
  320. return nil, ErrSignBadAlgo
  321. }
  322. mandatoryFlags["a"] = true
  323. case "b":
  324. //dkh.SignatureData = removeFWS(data)
  325. // remove all space
  326. dkh.SignatureData = strings.Replace(removeFWS(data), " ", "", -1)
  327. if len(dkh.SignatureData) != 0 {
  328. mandatoryFlags["b"] = true
  329. }
  330. case "bh":
  331. dkh.BodyHash = removeFWS(data)
  332. if len(dkh.BodyHash) != 0 {
  333. mandatoryFlags["bh"] = true
  334. }
  335. case "d":
  336. dkh.Domain = strings.ToLower(data)
  337. if len(dkh.Domain) != 0 {
  338. mandatoryFlags["d"] = true
  339. }
  340. case "h":
  341. data = strings.ToLower(data)
  342. dkh.Headers = strings.Split(data, ":")
  343. if len(dkh.Headers) != 0 {
  344. mandatoryFlags["h"] = true
  345. }
  346. fromFound := false
  347. for _, h := range dkh.Headers {
  348. if h == "from" {
  349. fromFound = true
  350. }
  351. }
  352. if !fromFound {
  353. return nil, ErrDkimHeaderNoFromInHTag
  354. }
  355. case "s":
  356. dkh.Selector = strings.ToLower(data)
  357. if len(dkh.Selector) != 0 {
  358. mandatoryFlags["s"] = true
  359. }
  360. case "c":
  361. dkh.MessageCanonicalization, err = validateCanonicalization(strings.ToLower(data))
  362. if err != nil {
  363. return nil, err
  364. }
  365. case "i":
  366. if data != "" {
  367. if !strings.HasSuffix(data, dkh.Domain) {
  368. return nil, ErrDkimHeaderDomainMismatch
  369. }
  370. dkh.Auid = data
  371. }
  372. case "l":
  373. ui, err := strconv.ParseUint(data, 10, 32)
  374. if err != nil {
  375. return nil, err
  376. }
  377. dkh.BodyLength = uint(ui)
  378. case "q":
  379. dkh.QueryMethods = strings.Split(data, ":")
  380. if len(dkh.QueryMethods) == 0 || strings.ToLower(dkh.QueryMethods[0]) != "dns/txt" {
  381. return nil, errQueryMethodNotsupported
  382. }
  383. case "t":
  384. ts, err := strconv.ParseInt(data, 10, 64)
  385. if err != nil {
  386. return nil, err
  387. }
  388. dkh.SignatureTimestamp = time.Unix(ts, 0)
  389. case "x":
  390. ts, err := strconv.ParseInt(data, 10, 64)
  391. if err != nil {
  392. return nil, err
  393. }
  394. dkh.SignatureExpiration = time.Unix(ts, 0)
  395. case "z":
  396. dkh.CopiedHeaderFields = strings.Split(data, "|")
  397. }
  398. }
  399. // All mandatory flags are in ?
  400. for _, p := range mandatoryFlags {
  401. if !p {
  402. return nil, ErrDkimHeaderMissingRequiredTag
  403. }
  404. }
  405. // default for i/Auid
  406. if dkh.Auid == "" {
  407. dkh.Auid = "@" + dkh.Domain
  408. }
  409. // defaut for query method
  410. if len(dkh.QueryMethods) == 0 {
  411. dkh.QueryMethods = []string{"dns/text"}
  412. }
  413. return dkh, nil
  414. }
  415. // GetHeaderBase return base header for signers
  416. // Todo: some refactoring needed...
  417. func (d *DKIMHeader) getHeaderBaseForSigning(bodyHash string) string {
  418. h := "DKIM-Signature: v=" + d.Version + "; a=" + d.Algorithm + "; q=" + strings.Join(d.QueryMethods, ":") + "; c=" + d.MessageCanonicalization + ";" + CRLF + TAB
  419. subh := "s=" + d.Selector + ";"
  420. if len(subh)+len(d.Domain)+4 > MaxHeaderLineLength {
  421. h += subh + FWS
  422. subh = ""
  423. }
  424. subh += " d=" + d.Domain + ";"
  425. // Auid
  426. if len(d.Auid) != 0 {
  427. if len(subh)+len(d.Auid)+4 > MaxHeaderLineLength {
  428. h += subh + FWS
  429. subh = ""
  430. }
  431. subh += " i=" + d.Auid + ";"
  432. }
  433. /*h := "DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=tmail.io; i=@tmail.io;" + FWS
  434. subh := "q=dns/txt; s=test;"*/
  435. // signature timestamp
  436. if !d.SignatureTimestamp.IsZero() {
  437. ts := d.SignatureTimestamp.Unix()
  438. if len(subh)+14 > MaxHeaderLineLength {
  439. h += subh + FWS
  440. subh = ""
  441. }
  442. subh += " t=" + fmt.Sprintf("%d", ts) + ";"
  443. }
  444. if len(subh)+len(d.Domain)+4 > MaxHeaderLineLength {
  445. h += subh + FWS
  446. subh = ""
  447. }
  448. // Expiration
  449. if !d.SignatureExpiration.IsZero() {
  450. ts := d.SignatureExpiration.Unix()
  451. if len(subh)+14 > MaxHeaderLineLength {
  452. h += subh + FWS
  453. subh = ""
  454. }
  455. subh += " x=" + fmt.Sprintf("%d", ts) + ";"
  456. }
  457. // body length
  458. if d.BodyLength != 0 {
  459. bodyLengthStr := fmt.Sprintf("%d", d.BodyLength)
  460. if len(subh)+len(bodyLengthStr)+4 > MaxHeaderLineLength {
  461. h += subh + FWS
  462. subh = ""
  463. }
  464. subh += " l=" + bodyLengthStr + ";"
  465. }
  466. // Headers
  467. if len(subh)+len(d.Headers)+4 > MaxHeaderLineLength {
  468. h += subh + FWS
  469. subh = ""
  470. }
  471. subh += " h="
  472. for _, header := range d.Headers {
  473. if len(subh)+len(header)+1 > MaxHeaderLineLength {
  474. h += subh + FWS
  475. subh = ""
  476. }
  477. subh += header + ":"
  478. }
  479. subh = subh[:len(subh)-1] + ";"
  480. // BodyHash
  481. if len(subh)+5+len(bodyHash) > MaxHeaderLineLength {
  482. h += subh + FWS
  483. subh = ""
  484. } else {
  485. subh += " "
  486. }
  487. subh += "bh="
  488. l := len(subh)
  489. for _, c := range bodyHash {
  490. subh += string(c)
  491. l++
  492. if l >= MaxHeaderLineLength {
  493. h += subh + FWS
  494. subh = ""
  495. l = 0
  496. }
  497. }
  498. h += subh + ";" + FWS + "b="
  499. return h
  500. }