hash.go 3.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  1. // Copyright (c) 2021 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 appstate
  7. import (
  8. "crypto/hmac"
  9. "crypto/sha256"
  10. "crypto/sha512"
  11. "encoding/binary"
  12. "fmt"
  13. "hash"
  14. "go.mau.fi/whatsmeow/appstate/lthash"
  15. "go.mau.fi/whatsmeow/proto/waServerSync"
  16. "go.mau.fi/whatsmeow/proto/waSyncAction"
  17. )
  18. type Mutation struct {
  19. Operation waServerSync.SyncdMutation_SyncdOperation
  20. Action *waSyncAction.SyncActionValue
  21. Version int32
  22. Index []string
  23. IndexMAC []byte
  24. ValueMAC []byte
  25. }
  26. type HashState struct {
  27. Version uint64
  28. Hash [128]byte
  29. }
  30. func (hs *HashState) updateHash(mutations []*waServerSync.SyncdMutation, getPrevSetValueMAC func(indexMAC []byte, maxIndex int) ([]byte, error)) ([]error, error) {
  31. var added, removed [][]byte
  32. var warnings []error
  33. for i, mutation := range mutations {
  34. if mutation.GetOperation() == waServerSync.SyncdMutation_SET {
  35. value := mutation.GetRecord().GetValue().GetBlob()
  36. added = append(added, value[len(value)-32:])
  37. }
  38. indexMAC := mutation.GetRecord().GetIndex().GetBlob()
  39. removal, err := getPrevSetValueMAC(indexMAC, i)
  40. if err != nil {
  41. return warnings, fmt.Errorf("failed to get value MAC of previous SET operation: %w", err)
  42. } else if removal != nil {
  43. removed = append(removed, removal)
  44. } else if mutation.GetOperation() == waServerSync.SyncdMutation_REMOVE {
  45. // TODO figure out if there are certain cases that are safe to ignore and others that aren't
  46. // At least removing contact access from WhatsApp seems to create a REMOVE op for your own JID
  47. // that points to a non-existent index and is safe to ignore here. Other keys might not be safe to ignore.
  48. warnings = append(warnings, fmt.Errorf("%w for %X", ErrMissingPreviousSetValueOperation, indexMAC))
  49. //return ErrMissingPreviousSetValueOperation
  50. }
  51. }
  52. lthash.WAPatchIntegrity.SubtractThenAddInPlace(hs.Hash[:], removed, added)
  53. return warnings, nil
  54. }
  55. func uint64ToBytes(val uint64) []byte {
  56. data := make([]byte, 8)
  57. binary.BigEndian.PutUint64(data, val)
  58. return data
  59. }
  60. func concatAndHMAC(alg func() hash.Hash, key []byte, data ...[]byte) []byte {
  61. h := hmac.New(alg, key)
  62. for _, item := range data {
  63. h.Write(item)
  64. }
  65. return h.Sum(nil)
  66. }
  67. func (hs *HashState) generateSnapshotMAC(name WAPatchName, key []byte) []byte {
  68. return concatAndHMAC(sha256.New, key, hs.Hash[:], uint64ToBytes(hs.Version), []byte(name))
  69. }
  70. func generatePatchMAC(patch *waServerSync.SyncdPatch, name WAPatchName, key []byte, version uint64) []byte {
  71. dataToHash := make([][]byte, len(patch.GetMutations())+3)
  72. dataToHash[0] = patch.GetSnapshotMAC()
  73. for i, mutation := range patch.Mutations {
  74. val := mutation.GetRecord().GetValue().GetBlob()
  75. dataToHash[i+1] = val[len(val)-32:]
  76. }
  77. dataToHash[len(dataToHash)-2] = uint64ToBytes(version)
  78. dataToHash[len(dataToHash)-1] = []byte(name)
  79. return concatAndHMAC(sha256.New, key, dataToHash...)
  80. }
  81. func generateContentMAC(operation waServerSync.SyncdMutation_SyncdOperation, data, keyID, key []byte) []byte {
  82. operationBytes := []byte{byte(operation) + 1}
  83. keyDataLength := uint64ToBytes(uint64(len(keyID) + 1))
  84. return concatAndHMAC(sha512.New, key, operationBytes, keyID, data, keyDataLength)[:32]
  85. }