msgsecret.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385
  1. // Copyright (c) 2022 Tulir Asokan
  2. //
  3. // This Source Code Form is subject to the terms of the Mozilla Public
  4. // License, v. 2.0. If a copy of the MPL was not distributed with this
  5. // file, You can obtain one at http://mozilla.org/MPL/2.0/.
  6. package whatsmeow
  7. import (
  8. "context"
  9. "crypto/sha256"
  10. "fmt"
  11. "time"
  12. "go.mau.fi/util/random"
  13. "google.golang.org/protobuf/proto"
  14. "git.bobomao.top/joey/testwh/proto/waCommon"
  15. "git.bobomao.top/joey/testwh/proto/waE2E"
  16. "git.bobomao.top/joey/testwh/types"
  17. "git.bobomao.top/joey/testwh/types/events"
  18. "git.bobomao.top/joey/testwh/util/gcmutil"
  19. "git.bobomao.top/joey/testwh/util/hkdfutil"
  20. )
  21. type MsgSecretType string
  22. const (
  23. EncSecretPollVote MsgSecretType = "Poll Vote"
  24. EncSecretReaction MsgSecretType = "Enc Reaction"
  25. EncSecretComment MsgSecretType = "Enc Comment"
  26. EncSecretReportToken MsgSecretType = "Report Token"
  27. EncSecretEventResponse MsgSecretType = "Event Response"
  28. EncSecretEventEdit MsgSecretType = "Event Edit"
  29. EncSecretBotMsg MsgSecretType = "Bot Message"
  30. )
  31. func applyBotMessageHKDF(messageSecret []byte) []byte {
  32. return hkdfutil.SHA256(messageSecret, nil, []byte(EncSecretBotMsg), 32)
  33. }
  34. func generateMsgSecretKey(
  35. modificationType MsgSecretType, modificationSender types.JID,
  36. origMsgID types.MessageID, origMsgSender types.JID, origMsgSecret []byte,
  37. ) ([]byte, []byte) {
  38. origMsgSenderStr := origMsgSender.ToNonAD().String()
  39. modificationSenderStr := modificationSender.ToNonAD().String()
  40. useCaseSecret := make([]byte, 0, len(origMsgID)+len(origMsgSenderStr)+len(modificationSenderStr)+len(modificationType))
  41. useCaseSecret = append(useCaseSecret, origMsgID...)
  42. useCaseSecret = append(useCaseSecret, origMsgSenderStr...)
  43. useCaseSecret = append(useCaseSecret, modificationSenderStr...)
  44. useCaseSecret = append(useCaseSecret, modificationType...)
  45. secretKey := hkdfutil.SHA256(origMsgSecret, nil, useCaseSecret, 32)
  46. var additionalData []byte
  47. switch modificationType {
  48. case EncSecretPollVote, EncSecretEventResponse, "":
  49. additionalData = fmt.Appendf(nil, "%s\x00%s", origMsgID, modificationSenderStr)
  50. }
  51. return secretKey, additionalData
  52. }
  53. func getOrigSenderFromKey(msg *events.Message, key *waCommon.MessageKey) (types.JID, error) {
  54. if key.GetFromMe() {
  55. // fromMe always means the poll and vote were sent by the same user
  56. // TODO this is wrong if the message key used @s.whatsapp.net, but the new event is from @lid
  57. return msg.Info.Sender, nil
  58. } else if msg.Info.Chat.Server == types.DefaultUserServer || msg.Info.Chat.Server == types.HiddenUserServer {
  59. sender, err := types.ParseJID(key.GetRemoteJID())
  60. if err != nil {
  61. return types.EmptyJID, fmt.Errorf("failed to parse JID %q of original message sender: %w", key.GetRemoteJID(), err)
  62. }
  63. return sender, nil
  64. } else {
  65. sender, err := types.ParseJID(key.GetParticipant())
  66. if sender.Server != types.DefaultUserServer && sender.Server != types.HiddenUserServer {
  67. err = fmt.Errorf("unexpected server")
  68. }
  69. if err != nil {
  70. return types.EmptyJID, fmt.Errorf("failed to parse JID %q of original message sender: %w", key.GetParticipant(), err)
  71. }
  72. return sender, nil
  73. }
  74. }
  75. type messageEncryptedSecret interface {
  76. GetEncIV() []byte
  77. GetEncPayload() []byte
  78. }
  79. func (cli *Client) decryptMsgSecret(ctx context.Context, msg *events.Message, useCase MsgSecretType, encrypted messageEncryptedSecret, origMsgKey *waCommon.MessageKey) ([]byte, error) {
  80. if cli == nil {
  81. return nil, ErrClientIsNil
  82. }
  83. origSender, err := getOrigSenderFromKey(msg, origMsgKey)
  84. if err != nil {
  85. return nil, err
  86. }
  87. baseEncKey, origSender, err := cli.Store.MsgSecrets.GetMessageSecret(ctx, msg.Info.Chat, origSender, origMsgKey.GetID())
  88. if err != nil {
  89. return nil, fmt.Errorf("failed to get original message secret key: %w", err)
  90. }
  91. if baseEncKey == nil {
  92. return nil, ErrOriginalMessageSecretNotFound
  93. }
  94. secretKey, additionalData := generateMsgSecretKey(useCase, msg.Info.Sender, origMsgKey.GetID(), origSender, baseEncKey)
  95. plaintext, err := gcmutil.Decrypt(secretKey, encrypted.GetEncIV(), encrypted.GetEncPayload(), additionalData)
  96. if err != nil {
  97. return nil, fmt.Errorf("failed to decrypt secret message: %w", err)
  98. }
  99. return plaintext, nil
  100. }
  101. func (cli *Client) encryptMsgSecret(ctx context.Context, ownID, chat, origSender types.JID, origMsgID types.MessageID, useCase MsgSecretType, plaintext []byte) (ciphertext, iv []byte, err error) {
  102. if cli == nil {
  103. return nil, nil, ErrClientIsNil
  104. } else if ownID.IsEmpty() {
  105. return nil, nil, ErrNotLoggedIn
  106. }
  107. baseEncKey, origSender, err := cli.Store.MsgSecrets.GetMessageSecret(ctx, chat, origSender, origMsgID)
  108. if err != nil {
  109. return nil, nil, fmt.Errorf("failed to get original message secret key: %w", err)
  110. } else if baseEncKey == nil {
  111. return nil, nil, ErrOriginalMessageSecretNotFound
  112. }
  113. secretKey, additionalData := generateMsgSecretKey(useCase, ownID, origMsgID, origSender, baseEncKey)
  114. iv = random.Bytes(12)
  115. ciphertext, err = gcmutil.Encrypt(secretKey, iv, plaintext, additionalData)
  116. if err != nil {
  117. return nil, nil, fmt.Errorf("failed to encrypt secret message: %w", err)
  118. }
  119. return ciphertext, iv, nil
  120. }
  121. func (cli *Client) decryptBotMessage(ctx context.Context, messageSecret []byte, msMsg messageEncryptedSecret, messageID types.MessageID, targetSenderJID types.JID, info *types.MessageInfo) ([]byte, error) {
  122. newKey, additionalData := generateMsgSecretKey("", info.Sender, messageID, targetSenderJID, applyBotMessageHKDF(messageSecret))
  123. plaintext, err := gcmutil.Decrypt(newKey, msMsg.GetEncIV(), msMsg.GetEncPayload(), additionalData)
  124. if err != nil {
  125. return nil, fmt.Errorf("failed to decrypt secret message: %w", err)
  126. }
  127. return plaintext, nil
  128. }
  129. // DecryptReaction decrypts a reaction message in a community announcement group.
  130. //
  131. // if evt.Message.GetEncReactionMessage() != nil {
  132. // reaction, err := cli.DecryptReaction(evt)
  133. // if err != nil {
  134. // fmt.Println(":(", err)
  135. // return
  136. // }
  137. // fmt.Printf("Reaction message: %+v\n", reaction)
  138. // }
  139. func (cli *Client) DecryptReaction(ctx context.Context, reaction *events.Message) (*waE2E.ReactionMessage, error) {
  140. encReaction := reaction.Message.GetEncReactionMessage()
  141. if encReaction == nil {
  142. return nil, ErrNotEncryptedReactionMessage
  143. }
  144. plaintext, err := cli.decryptMsgSecret(ctx, reaction, EncSecretReaction, encReaction, encReaction.GetTargetMessageKey())
  145. if err != nil {
  146. return nil, fmt.Errorf("failed to decrypt reaction: %w", err)
  147. }
  148. var msg waE2E.ReactionMessage
  149. err = proto.Unmarshal(plaintext, &msg)
  150. if err != nil {
  151. return nil, fmt.Errorf("failed to decode reaction protobuf: %w", err)
  152. }
  153. return &msg, nil
  154. }
  155. // DecryptComment decrypts a reply/comment message in a community announcement group.
  156. //
  157. // if evt.Message.GetEncCommentMessage() != nil {
  158. // comment, err := cli.DecryptComment(evt)
  159. // if err != nil {
  160. // fmt.Println(":(", err)
  161. // return
  162. // }
  163. // fmt.Printf("Comment message: %+v\n", comment)
  164. // }
  165. func (cli *Client) DecryptComment(ctx context.Context, comment *events.Message) (*waE2E.Message, error) {
  166. encComment := comment.Message.GetEncCommentMessage()
  167. if encComment == nil {
  168. return nil, ErrNotEncryptedCommentMessage
  169. }
  170. plaintext, err := cli.decryptMsgSecret(ctx, comment, EncSecretComment, encComment, encComment.GetTargetMessageKey())
  171. if err != nil {
  172. return nil, fmt.Errorf("failed to decrypt comment: %w", err)
  173. }
  174. var msg waE2E.Message
  175. err = proto.Unmarshal(plaintext, &msg)
  176. if err != nil {
  177. return nil, fmt.Errorf("failed to decode comment protobuf: %w", err)
  178. }
  179. return &msg, nil
  180. }
  181. // DecryptPollVote decrypts a poll update message. The vote itself includes SHA-256 hashes of the selected options.
  182. //
  183. // if evt.Message.GetPollUpdateMessage() != nil {
  184. // pollVote, err := cli.DecryptPollVote(evt)
  185. // if err != nil {
  186. // fmt.Println(":(", err)
  187. // return
  188. // }
  189. // fmt.Println("Selected hashes:")
  190. // for _, hash := range pollVote.GetSelectedOptions() {
  191. // fmt.Printf("- %X\n", hash)
  192. // }
  193. // }
  194. func (cli *Client) DecryptPollVote(ctx context.Context, vote *events.Message) (*waE2E.PollVoteMessage, error) {
  195. pollUpdate := vote.Message.GetPollUpdateMessage()
  196. if pollUpdate == nil {
  197. return nil, ErrNotPollUpdateMessage
  198. }
  199. plaintext, err := cli.decryptMsgSecret(ctx, vote, EncSecretPollVote, pollUpdate.GetVote(), pollUpdate.GetPollCreationMessageKey())
  200. if err != nil {
  201. return nil, fmt.Errorf("failed to decrypt poll vote: %w", err)
  202. }
  203. var msg waE2E.PollVoteMessage
  204. err = proto.Unmarshal(plaintext, &msg)
  205. if err != nil {
  206. return nil, fmt.Errorf("failed to decode poll vote protobuf: %w", err)
  207. }
  208. return &msg, nil
  209. }
  210. func (cli *Client) DecryptSecretEncryptedMessage(ctx context.Context, evt *events.Message) (*waE2E.Message, error) {
  211. encMessage := evt.Message.GetSecretEncryptedMessage()
  212. if encMessage == nil {
  213. return nil, ErrNotSecretEncryptedMessage
  214. }
  215. if encMessage.GetSecretEncType() != waE2E.SecretEncryptedMessage_EVENT_EDIT {
  216. return nil, fmt.Errorf("unsupported secret enc type: %s", encMessage.SecretEncType.String())
  217. }
  218. plaintext, err := cli.decryptMsgSecret(ctx, evt, EncSecretEventEdit, encMessage, encMessage.GetTargetMessageKey())
  219. if err != nil {
  220. return nil, fmt.Errorf("failed to decrypt message: %w", err)
  221. }
  222. var msg waE2E.Message
  223. err = proto.Unmarshal(plaintext, &msg)
  224. if err != nil {
  225. return nil, fmt.Errorf("failed to decode message protobuf: %w", err)
  226. }
  227. if evt.Message.MessageContextInfo != nil && msg.MessageContextInfo == nil {
  228. msg.MessageContextInfo = evt.Message.MessageContextInfo
  229. }
  230. return &msg, nil
  231. }
  232. func getKeyFromInfo(msgInfo *types.MessageInfo) *waCommon.MessageKey {
  233. creationKey := &waCommon.MessageKey{
  234. RemoteJID: proto.String(msgInfo.Chat.String()),
  235. FromMe: proto.Bool(msgInfo.IsFromMe),
  236. ID: proto.String(msgInfo.ID),
  237. }
  238. if msgInfo.IsGroup {
  239. creationKey.Participant = proto.String(msgInfo.Sender.String())
  240. }
  241. return creationKey
  242. }
  243. // HashPollOptions hashes poll option names using SHA-256 for voting.
  244. // This is used by BuildPollVote to convert selected option names to hashes.
  245. func HashPollOptions(optionNames []string) [][]byte {
  246. optionHashes := make([][]byte, len(optionNames))
  247. for i, option := range optionNames {
  248. optionHash := sha256.Sum256([]byte(option))
  249. optionHashes[i] = optionHash[:]
  250. }
  251. return optionHashes
  252. }
  253. // BuildPollVote builds a poll vote message using the given poll message info and option names.
  254. // The built message can be sent normally using Client.SendMessage.
  255. //
  256. // For example, to vote for the first option after receiving a message event (*events.Message):
  257. //
  258. // if evt.Message.GetPollCreationMessage() != nil {
  259. // pollVoteMsg, err := cli.BuildPollVote(&evt.Info, []string{evt.Message.GetPollCreationMessage().GetOptions()[0].GetOptionName()})
  260. // if err != nil {
  261. // fmt.Println(":(", err)
  262. // return
  263. // }
  264. // resp, err := cli.SendMessage(context.Background(), evt.Info.Chat, pollVoteMsg)
  265. // }
  266. func (cli *Client) BuildPollVote(ctx context.Context, pollInfo *types.MessageInfo, optionNames []string) (*waE2E.Message, error) {
  267. pollUpdate, err := cli.EncryptPollVote(ctx, pollInfo, &waE2E.PollVoteMessage{
  268. SelectedOptions: HashPollOptions(optionNames),
  269. })
  270. return &waE2E.Message{PollUpdateMessage: pollUpdate}, err
  271. }
  272. // BuildPollCreation builds a poll creation message with the given poll name, options and maximum number of selections.
  273. // The built message can be sent normally using Client.SendMessage.
  274. //
  275. // resp, err := cli.SendMessage(context.Background(), chat, cli.BuildPollCreation("meow?", []string{"yes", "no"}, 1))
  276. func (cli *Client) BuildPollCreation(name string, optionNames []string, selectableOptionCount int) *waE2E.Message {
  277. msgSecret := random.Bytes(32)
  278. if selectableOptionCount < 0 || selectableOptionCount > len(optionNames) {
  279. selectableOptionCount = 0
  280. }
  281. options := make([]*waE2E.PollCreationMessage_Option, len(optionNames))
  282. for i, option := range optionNames {
  283. options[i] = &waE2E.PollCreationMessage_Option{OptionName: proto.String(option)}
  284. }
  285. return &waE2E.Message{
  286. PollCreationMessage: &waE2E.PollCreationMessage{
  287. Name: proto.String(name),
  288. Options: options,
  289. SelectableOptionsCount: proto.Uint32(uint32(selectableOptionCount)),
  290. },
  291. MessageContextInfo: &waE2E.MessageContextInfo{
  292. MessageSecret: msgSecret,
  293. },
  294. }
  295. }
  296. // EncryptPollVote encrypts a poll vote message. This is a slightly lower-level function, using BuildPollVote is recommended.
  297. func (cli *Client) EncryptPollVote(ctx context.Context, pollInfo *types.MessageInfo, vote *waE2E.PollVoteMessage) (*waE2E.PollUpdateMessage, error) {
  298. plaintext, err := proto.Marshal(vote)
  299. if err != nil {
  300. return nil, fmt.Errorf("failed to marshal poll vote protobuf: %w", err)
  301. }
  302. ciphertext, iv, err := cli.encryptMsgSecret(ctx, cli.getOwnID(), pollInfo.Chat, pollInfo.Sender, pollInfo.ID, EncSecretPollVote, plaintext)
  303. if err != nil {
  304. return nil, fmt.Errorf("failed to encrypt poll vote: %w", err)
  305. }
  306. return &waE2E.PollUpdateMessage{
  307. PollCreationMessageKey: getKeyFromInfo(pollInfo),
  308. Vote: &waE2E.PollEncValue{
  309. EncPayload: ciphertext,
  310. EncIV: iv,
  311. },
  312. SenderTimestampMS: proto.Int64(time.Now().UnixMilli()),
  313. }, nil
  314. }
  315. func (cli *Client) EncryptComment(ctx context.Context, rootMsgInfo *types.MessageInfo, comment *waE2E.Message) (*waE2E.Message, error) {
  316. plaintext, err := proto.Marshal(comment)
  317. if err != nil {
  318. return nil, fmt.Errorf("failed to marshal comment protobuf: %w", err)
  319. }
  320. // TODO is hardcoding LID here correct? What about polls?
  321. ciphertext, iv, err := cli.encryptMsgSecret(ctx, cli.getOwnLID(), rootMsgInfo.Chat, rootMsgInfo.Sender, rootMsgInfo.ID, EncSecretComment, plaintext)
  322. if err != nil {
  323. return nil, fmt.Errorf("failed to encrypt comment: %w", err)
  324. }
  325. return &waE2E.Message{
  326. EncCommentMessage: &waE2E.EncCommentMessage{
  327. TargetMessageKey: &waCommon.MessageKey{
  328. RemoteJID: proto.String(rootMsgInfo.Chat.String()),
  329. Participant: proto.String(rootMsgInfo.Sender.ToNonAD().String()),
  330. FromMe: proto.Bool(rootMsgInfo.IsFromMe),
  331. ID: proto.String(rootMsgInfo.ID),
  332. },
  333. EncPayload: ciphertext,
  334. EncIV: iv,
  335. },
  336. }, nil
  337. }
  338. func (cli *Client) EncryptReaction(ctx context.Context, rootMsgInfo *types.MessageInfo, reaction *waE2E.ReactionMessage) (*waE2E.EncReactionMessage, error) {
  339. reactionKey := reaction.Key
  340. reaction.Key = nil
  341. plaintext, err := proto.Marshal(reaction)
  342. if err != nil {
  343. return nil, fmt.Errorf("failed to marshal reaction protobuf: %w", err)
  344. }
  345. ciphertext, iv, err := cli.encryptMsgSecret(ctx, cli.getOwnLID(), rootMsgInfo.Chat, rootMsgInfo.Sender, rootMsgInfo.ID, EncSecretReaction, plaintext)
  346. if err != nil {
  347. return nil, fmt.Errorf("failed to encrypt reaction: %w", err)
  348. }
  349. return &waE2E.EncReactionMessage{
  350. TargetMessageKey: reactionKey,
  351. EncPayload: ciphertext,
  352. EncIV: iv,
  353. }, nil
  354. }