decrypt.go 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293
  1. // Copyright (C) 2021 The Syncthing Authors.
  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 file,
  5. // You can obtain one at https://mozilla.org/MPL/2.0/.
  6. // Package decrypt implements the `syncthing decrypt` subcommand.
  7. package decrypt
  8. import (
  9. "encoding/binary"
  10. "encoding/json"
  11. "errors"
  12. "fmt"
  13. "io"
  14. "log"
  15. "os"
  16. "path/filepath"
  17. "google.golang.org/protobuf/proto"
  18. "github.com/syncthing/syncthing/internal/gen/bep"
  19. "github.com/syncthing/syncthing/lib/config"
  20. "github.com/syncthing/syncthing/lib/fs"
  21. "github.com/syncthing/syncthing/lib/osutil"
  22. "github.com/syncthing/syncthing/lib/protocol"
  23. "github.com/syncthing/syncthing/lib/scanner"
  24. )
  25. type CLI struct {
  26. Path string `arg:"" required:"1" help:"Path to encrypted folder"`
  27. To string `xor:"mode" placeholder:"PATH" help:"Destination directory, when decrypting"`
  28. VerifyOnly bool `xor:"mode" help:"Don't write decrypted files to disk (but verify plaintext hashes)"`
  29. Password string `help:"Folder password for decryption / verification" env:"FOLDER_PASSWORD"`
  30. FolderID string `help:"Folder ID of the encrypted folder, if it cannot be determined automatically"`
  31. Continue bool `help:"Continue processing next file in case of error, instead of aborting"`
  32. Verbose bool `help:"Show verbose progress information"`
  33. TokenPath string `placeholder:"PATH" help:"Path to the token file within the folder (used to determine folder ID)"`
  34. folderKey *[32]byte
  35. keyGen *protocol.KeyGenerator
  36. }
  37. type storedEncryptionToken struct {
  38. FolderID string
  39. Token []byte
  40. }
  41. func (c *CLI) Run() error {
  42. log.SetFlags(0)
  43. if c.To == "" && !c.VerifyOnly {
  44. return errors.New("must set --to or --verify-only")
  45. }
  46. if c.TokenPath == "" {
  47. // This is a bit long to show as default in --help
  48. c.TokenPath = filepath.Join(config.DefaultMarkerName, config.EncryptionTokenName)
  49. }
  50. if c.FolderID == "" {
  51. // We should try to figure out the folder ID
  52. folderID, err := c.getFolderID()
  53. if err != nil {
  54. log.Println("No --folder-id given and couldn't read folder token")
  55. return fmt.Errorf("getting folder ID: %w", err)
  56. }
  57. c.FolderID = folderID
  58. if c.Verbose {
  59. log.Println("Found folder ID:", c.FolderID)
  60. }
  61. }
  62. c.keyGen = protocol.NewKeyGenerator()
  63. c.folderKey = c.keyGen.KeyFromPassword(c.FolderID, c.Password)
  64. return c.walk()
  65. }
  66. // walk finds and processes every file in the encrypted folder
  67. func (c *CLI) walk() error {
  68. srcFs := fs.NewFilesystem(fs.FilesystemTypeBasic, c.Path)
  69. var dstFs fs.Filesystem
  70. if c.To != "" {
  71. dstFs = fs.NewFilesystem(fs.FilesystemTypeBasic, c.To)
  72. }
  73. return srcFs.Walk(".", func(path string, info fs.FileInfo, err error) error {
  74. if err != nil {
  75. return err
  76. }
  77. if !info.IsRegular() {
  78. return nil
  79. }
  80. if fs.IsInternal(path) {
  81. return nil
  82. }
  83. return c.withContinue(c.process(srcFs, dstFs, path))
  84. })
  85. }
  86. // If --continue was set we just mention the error and return nil to
  87. // continue processing.
  88. func (c *CLI) withContinue(err error) error {
  89. if err == nil {
  90. return nil
  91. }
  92. if c.Continue {
  93. log.Println("Warning:", err)
  94. return nil
  95. }
  96. return err
  97. }
  98. // getFolderID returns the folder ID found in the encrypted token, or an
  99. // error.
  100. func (c *CLI) getFolderID() (string, error) {
  101. tokenPath := filepath.Join(c.Path, c.TokenPath)
  102. bs, err := os.ReadFile(tokenPath)
  103. if err != nil {
  104. return "", fmt.Errorf("reading folder token: %w", err)
  105. }
  106. var tok storedEncryptionToken
  107. if err := json.Unmarshal(bs, &tok); err != nil {
  108. return "", fmt.Errorf("parsing folder token: %w", err)
  109. }
  110. return tok.FolderID, nil
  111. }
  112. // process handles the file named path in srcFs, decrypting it into dstFs
  113. // unless dstFs is nil.
  114. func (c *CLI) process(srcFs fs.Filesystem, dstFs fs.Filesystem, path string) error {
  115. // Which filemode bits to preserve
  116. const retainBits = fs.ModePerm | fs.ModeSetgid | fs.ModeSetuid | fs.ModeSticky
  117. if c.Verbose {
  118. log.Printf("Processing %q", path)
  119. }
  120. encFd, err := srcFs.Open(path)
  121. if err != nil {
  122. return err
  123. }
  124. defer encFd.Close()
  125. encFi, err := loadEncryptedFileInfo(encFd)
  126. if err != nil {
  127. return fmt.Errorf("%s: loading metadata trailer: %w", path, err)
  128. }
  129. // Workaround for a bug in <= v1.15.0-rc.5 where we stored names
  130. // in native format, while protocol expects wire format (slashes).
  131. encFi.Name = osutil.NormalizedFilename(encFi.Name)
  132. plainFi, err := protocol.DecryptFileInfo(c.keyGen, *encFi, c.folderKey)
  133. if err != nil {
  134. return fmt.Errorf("%s: decrypting metadata: %w", path, err)
  135. }
  136. if c.Verbose {
  137. log.Printf("Plaintext filename is %q", plainFi.Name)
  138. }
  139. var plainFd fs.File
  140. if dstFs != nil {
  141. if err := dstFs.MkdirAll(filepath.Dir(plainFi.Name), 0o700); err != nil {
  142. return fmt.Errorf("%s: %w", plainFi.Name, err)
  143. }
  144. plainFd, err = dstFs.Create(plainFi.Name)
  145. if err != nil {
  146. return fmt.Errorf("%s: %w", plainFi.Name, err)
  147. }
  148. defer plainFd.Close() // also closed explicitly in the return
  149. if err := dstFs.Chmod(plainFi.Name, fs.FileMode(plainFi.Permissions&uint32(retainBits))); err != nil {
  150. return fmt.Errorf("%s: %w", plainFi.Name, err)
  151. }
  152. }
  153. if err := c.decryptFile(encFi, &plainFi, encFd, plainFd); err != nil {
  154. // Decrypting the file failed, leaving it in an inconsistent state.
  155. // Delete it. Even --continue currently doesn't mean "leave broken
  156. // stuff in place", it just means "try the next file instead of
  157. // aborting".
  158. if plainFd != nil {
  159. _ = dstFs.Remove(plainFd.Name())
  160. }
  161. return fmt.Errorf("%s: %s: %w", path, plainFi.Name, err)
  162. } else if c.Verbose {
  163. log.Printf("Data verified for %q", plainFi.Name)
  164. }
  165. if plainFd != nil {
  166. if err := plainFd.Close(); err != nil {
  167. return fmt.Errorf("%s: %w", plainFi.Name, err)
  168. }
  169. if err := dstFs.Chtimes(plainFi.Name, plainFi.ModTime(), plainFi.ModTime()); err != nil {
  170. return fmt.Errorf("%s: %w", plainFi.Name, err)
  171. }
  172. }
  173. return nil
  174. }
  175. // decryptFile reads, decrypts and verifies all the blocks in src, writing
  176. // it to dst if dst is non-nil. (If dst is nil it just becomes a
  177. // read-and-verify operation.)
  178. func (c *CLI) decryptFile(encFi *protocol.FileInfo, plainFi *protocol.FileInfo, src io.ReaderAt, dst io.WriterAt) error {
  179. // The encrypted and plaintext files must consist of an equal number of blocks
  180. if len(encFi.Blocks) != len(plainFi.Blocks) {
  181. return fmt.Errorf("block count mismatch: encrypted %d != plaintext %d", len(encFi.Blocks), len(plainFi.Blocks))
  182. }
  183. fileKey := c.keyGen.FileKey(plainFi.Name, c.folderKey)
  184. for i, encBlock := range encFi.Blocks {
  185. // Read the encrypted block
  186. buf := make([]byte, encBlock.Size)
  187. if _, err := src.ReadAt(buf, encBlock.Offset); err != nil {
  188. return fmt.Errorf("encrypted block %d (%d bytes): %w", i, encBlock.Size, err)
  189. }
  190. // Decrypt it
  191. dec, err := protocol.DecryptBytes(buf, fileKey)
  192. if err != nil {
  193. return fmt.Errorf("encrypted block %d (%d bytes): %w", i, encBlock.Size, err)
  194. }
  195. // Verify the block size against the expected plaintext
  196. plainBlock := plainFi.Blocks[i]
  197. if i == len(plainFi.Blocks)-1 && len(dec) > plainBlock.Size {
  198. // The last block might be padded, which is fine (we skip the padding)
  199. dec = dec[:plainBlock.Size]
  200. } else if len(dec) != plainBlock.Size {
  201. return fmt.Errorf("plaintext block %d size mismatch, actual %d != expected %d", i, len(dec), plainBlock.Size)
  202. }
  203. // Verify the hash against the plaintext block info
  204. if !scanner.Validate(dec, plainBlock.Hash, 0) {
  205. // The block decrypted correctly but fails the hash check. This
  206. // is odd and unexpected, but it it's still a valid block from
  207. // the source. The file might have changed while we pulled it?
  208. err := fmt.Errorf("plaintext block %d (%d bytes) failed validation after decryption", i, plainBlock.Size)
  209. if c.Continue {
  210. log.Printf("Warning: %s: %s: %v", encFi.Name, plainFi.Name, err)
  211. } else {
  212. return err
  213. }
  214. }
  215. // Write it to the destination, unless we're just verifying.
  216. if dst != nil {
  217. if _, err := dst.WriteAt(dec, plainBlock.Offset); err != nil {
  218. return err
  219. }
  220. }
  221. }
  222. return nil
  223. }
  224. // loadEncryptedFileInfo loads the encrypted FileInfo trailer from a file on
  225. // disk.
  226. func loadEncryptedFileInfo(fd fs.File) (*protocol.FileInfo, error) {
  227. // Seek to the size of the trailer block
  228. if _, err := fd.Seek(-4, io.SeekEnd); err != nil {
  229. return nil, err
  230. }
  231. var bs [4]byte
  232. if _, err := io.ReadFull(fd, bs[:]); err != nil {
  233. return nil, err
  234. }
  235. size := int64(binary.BigEndian.Uint32(bs[:]))
  236. // Seek to the start of the trailer
  237. if _, err := fd.Seek(-(4 + size), io.SeekEnd); err != nil {
  238. return nil, err
  239. }
  240. trailer := make([]byte, size)
  241. if _, err := io.ReadFull(fd, trailer); err != nil {
  242. return nil, err
  243. }
  244. var encFi bep.FileInfo
  245. if err := proto.Unmarshal(trailer, &encFi); err != nil {
  246. return nil, err
  247. }
  248. fi := protocol.FileInfoFromWire(&encFi)
  249. return &fi, nil
  250. }