cryptfs.go 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402
  1. // Copyright (C) 2019 Nicola Murino
  2. //
  3. // This program is free software: you can redistribute it and/or modify
  4. // it under the terms of the GNU Affero General Public License as published
  5. // by the Free Software Foundation, version 3.
  6. //
  7. // This program is distributed in the hope that it will be useful,
  8. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. // GNU Affero General Public License for more details.
  11. //
  12. // You should have received a copy of the GNU Affero General Public License
  13. // along with this program. If not, see <https://www.gnu.org/licenses/>.
  14. package vfs
  15. import (
  16. "bufio"
  17. "bytes"
  18. "crypto/rand"
  19. "crypto/sha256"
  20. "fmt"
  21. "io"
  22. "net/http"
  23. "os"
  24. "github.com/eikenb/pipeat"
  25. "github.com/minio/sio"
  26. "golang.org/x/crypto/hkdf"
  27. "github.com/drakkan/sftpgo/v2/internal/logger"
  28. )
  29. const (
  30. // cryptFsName is the name for the local Fs implementation with encryption support
  31. cryptFsName = "cryptfs"
  32. version10 byte = 0x10
  33. nonceV10Size int = 32
  34. headerV10Size int64 = 33 // 1 (version byte) + 32 (nonce size)
  35. )
  36. // CryptFs is a Fs implementation that allows to encrypts/decrypts local files
  37. type CryptFs struct {
  38. *OsFs
  39. localTempDir string
  40. masterKey []byte
  41. }
  42. // NewCryptFs returns a CryptFs object
  43. func NewCryptFs(connectionID, rootDir, mountPath string, config CryptFsConfig) (Fs, error) {
  44. if err := config.validate(); err != nil {
  45. return nil, err
  46. }
  47. if err := config.Passphrase.TryDecrypt(); err != nil {
  48. return nil, err
  49. }
  50. fs := &CryptFs{
  51. OsFs: &OsFs{
  52. name: cryptFsName,
  53. connectionID: connectionID,
  54. rootDir: rootDir,
  55. mountPath: getMountPath(mountPath),
  56. readBufferSize: config.OSFsConfig.ReadBufferSize * 1024 * 1024,
  57. writeBufferSize: config.OSFsConfig.WriteBufferSize * 1024 * 1024,
  58. },
  59. masterKey: []byte(config.Passphrase.GetPayload()),
  60. }
  61. if tempPath == "" {
  62. fs.localTempDir = rootDir
  63. } else {
  64. fs.localTempDir = tempPath
  65. }
  66. return fs, nil
  67. }
  68. // Name returns the name for the Fs implementation
  69. func (fs *CryptFs) Name() string {
  70. return fs.name
  71. }
  72. // Open opens the named file for reading
  73. func (fs *CryptFs) Open(name string, offset int64) (File, PipeReader, func(), error) {
  74. f, key, err := fs.getFileAndEncryptionKey(name)
  75. if err != nil {
  76. return nil, nil, nil, err
  77. }
  78. isZeroDownload, err := isZeroBytesDownload(f, offset)
  79. if err != nil {
  80. f.Close()
  81. return nil, nil, nil, err
  82. }
  83. r, w, err := pipeat.PipeInDir(fs.localTempDir)
  84. if err != nil {
  85. f.Close()
  86. return nil, nil, nil, err
  87. }
  88. p := NewPipeReader(r)
  89. go func() {
  90. if isZeroDownload {
  91. w.CloseWithError(err) //nolint:errcheck
  92. f.Close()
  93. fsLog(fs, logger.LevelDebug, "zero bytes download completed, path: %q", name)
  94. return
  95. }
  96. var n int64
  97. var err error
  98. if offset == 0 {
  99. n, err = fs.decryptWrapper(w, f, fs.getSIOConfig(key))
  100. } else {
  101. var readerAt io.ReaderAt
  102. var readed, written int
  103. buf := make([]byte, 65568)
  104. wrapper := &cryptedFileWrapper{
  105. File: f,
  106. }
  107. readerAt, err = sio.DecryptReaderAt(wrapper, fs.getSIOConfig(key))
  108. if err == nil {
  109. finished := false
  110. for !finished {
  111. readed, err = readerAt.ReadAt(buf, offset)
  112. offset += int64(readed)
  113. if err != nil && err != io.EOF {
  114. break
  115. }
  116. if err == io.EOF {
  117. finished = true
  118. err = nil
  119. }
  120. if readed > 0 {
  121. written, err = w.Write(buf[:readed])
  122. n += int64(written)
  123. if err != nil {
  124. if err == io.EOF {
  125. err = io.ErrUnexpectedEOF
  126. }
  127. break
  128. }
  129. if readed != written {
  130. err = io.ErrShortWrite
  131. break
  132. }
  133. }
  134. }
  135. }
  136. }
  137. w.CloseWithError(err) //nolint:errcheck
  138. f.Close()
  139. fsLog(fs, logger.LevelDebug, "download completed, path: %q size: %v, err: %v", name, n, err)
  140. }()
  141. return nil, p, nil, nil
  142. }
  143. // Create creates or opens the named file for writing
  144. func (fs *CryptFs) Create(name string, _, _ int) (File, PipeWriter, func(), error) {
  145. f, err := os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
  146. if err != nil {
  147. return nil, nil, nil, err
  148. }
  149. header := encryptedFileHeader{
  150. version: version10,
  151. nonce: make([]byte, 32),
  152. }
  153. _, err = io.ReadFull(rand.Reader, header.nonce)
  154. if err != nil {
  155. f.Close()
  156. return nil, nil, nil, err
  157. }
  158. var key [32]byte
  159. kdf := hkdf.New(sha256.New, fs.masterKey, header.nonce, nil)
  160. _, err = io.ReadFull(kdf, key[:])
  161. if err != nil {
  162. f.Close()
  163. return nil, nil, nil, err
  164. }
  165. r, w, err := pipeat.PipeInDir(fs.localTempDir)
  166. if err != nil {
  167. f.Close()
  168. return nil, nil, nil, err
  169. }
  170. err = header.Store(f)
  171. if err != nil {
  172. r.Close()
  173. w.Close()
  174. f.Close()
  175. return nil, nil, nil, err
  176. }
  177. p := NewPipeWriter(w)
  178. go func() {
  179. var n int64
  180. var err error
  181. if fs.writeBufferSize <= 0 {
  182. n, err = sio.Encrypt(f, r, fs.getSIOConfig(key))
  183. } else {
  184. bw := bufio.NewWriterSize(f, fs.writeBufferSize)
  185. n, err = fs.encryptWrapper(bw, r, fs.getSIOConfig(key))
  186. errFlush := bw.Flush()
  187. if err == nil && errFlush != nil {
  188. err = errFlush
  189. }
  190. }
  191. errClose := f.Close()
  192. if err == nil && errClose != nil {
  193. err = errClose
  194. }
  195. r.CloseWithError(err) //nolint:errcheck
  196. p.Done(err)
  197. fsLog(fs, logger.LevelDebug, "upload completed, path: %q, readed bytes: %v, err: %v", name, n, err)
  198. }()
  199. return nil, p, nil, nil
  200. }
  201. // Truncate changes the size of the named file
  202. func (*CryptFs) Truncate(_ string, _ int64) error {
  203. return ErrVfsUnsupported
  204. }
  205. // ReadDir reads the directory named by dirname and returns
  206. // a list of directory entries.
  207. func (fs *CryptFs) ReadDir(dirname string) ([]os.FileInfo, error) {
  208. f, err := os.Open(dirname)
  209. if err != nil {
  210. return nil, err
  211. }
  212. list, err := f.Readdir(-1)
  213. f.Close()
  214. if err != nil {
  215. return nil, err
  216. }
  217. result := make([]os.FileInfo, 0, len(list))
  218. for _, info := range list {
  219. result = append(result, fs.ConvertFileInfo(info))
  220. }
  221. return result, nil
  222. }
  223. // IsUploadResumeSupported returns false sio does not support random access writes
  224. func (*CryptFs) IsUploadResumeSupported() bool {
  225. return false
  226. }
  227. // IsConditionalUploadResumeSupported returns if resuming uploads is supported
  228. // for the specified size
  229. func (*CryptFs) IsConditionalUploadResumeSupported(_ int64) bool {
  230. return false
  231. }
  232. // GetMimeType returns the content type
  233. func (fs *CryptFs) GetMimeType(name string) (string, error) {
  234. f, key, err := fs.getFileAndEncryptionKey(name)
  235. if err != nil {
  236. return "", err
  237. }
  238. defer f.Close()
  239. readSize, err := sio.DecryptedSize(512)
  240. if err != nil {
  241. return "", err
  242. }
  243. buf := make([]byte, readSize)
  244. n, err := io.ReadFull(f, buf)
  245. if err != nil && err != io.EOF && err != io.ErrUnexpectedEOF {
  246. return "", err
  247. }
  248. decrypted := bytes.NewBuffer(nil)
  249. _, err = sio.Decrypt(decrypted, bytes.NewBuffer(buf[:n]), fs.getSIOConfig(key))
  250. if err != nil {
  251. return "", err
  252. }
  253. ctype := http.DetectContentType(decrypted.Bytes())
  254. // Rewind file.
  255. _, err = f.Seek(0, io.SeekStart)
  256. return ctype, err
  257. }
  258. func (fs *CryptFs) getSIOConfig(key [32]byte) sio.Config {
  259. return sio.Config{
  260. MinVersion: sio.Version20,
  261. MaxVersion: sio.Version20,
  262. Key: key[:],
  263. }
  264. }
  265. // ConvertFileInfo returns a FileInfo with the decrypted size
  266. func (fs *CryptFs) ConvertFileInfo(info os.FileInfo) os.FileInfo {
  267. if !info.Mode().IsRegular() {
  268. return info
  269. }
  270. size := info.Size()
  271. if size >= headerV10Size {
  272. size -= headerV10Size
  273. decryptedSize, err := sio.DecryptedSize(uint64(size))
  274. if err == nil {
  275. size = int64(decryptedSize)
  276. }
  277. } else {
  278. size = 0
  279. }
  280. return NewFileInfo(info.Name(), info.IsDir(), size, info.ModTime(), false)
  281. }
  282. func (fs *CryptFs) getFileAndEncryptionKey(name string) (*os.File, [32]byte, error) {
  283. var key [32]byte
  284. f, err := os.Open(name)
  285. if err != nil {
  286. return nil, key, err
  287. }
  288. header := encryptedFileHeader{}
  289. err = header.Load(f)
  290. if err != nil {
  291. f.Close()
  292. return nil, key, err
  293. }
  294. kdf := hkdf.New(sha256.New, fs.masterKey, header.nonce, nil)
  295. _, err = io.ReadFull(kdf, key[:])
  296. if err != nil {
  297. f.Close()
  298. return nil, key, err
  299. }
  300. return f, key, err
  301. }
  302. func (*CryptFs) encryptWrapper(dst io.Writer, src io.Reader, config sio.Config) (int64, error) {
  303. encReader, err := sio.EncryptReader(src, config)
  304. if err != nil {
  305. return 0, err
  306. }
  307. return doCopy(dst, encReader, make([]byte, 65568))
  308. }
  309. func (fs *CryptFs) decryptWrapper(dst io.Writer, src io.Reader, config sio.Config) (int64, error) {
  310. if fs.readBufferSize <= 0 {
  311. return sio.Decrypt(dst, src, config)
  312. }
  313. br := bufio.NewReaderSize(src, fs.readBufferSize)
  314. decReader, err := sio.DecryptReader(br, config)
  315. if err != nil {
  316. return 0, err
  317. }
  318. return doCopy(dst, decReader, make([]byte, 65568))
  319. }
  320. func isZeroBytesDownload(f *os.File, offset int64) (bool, error) {
  321. info, err := f.Stat()
  322. if err != nil {
  323. return false, err
  324. }
  325. if info.Size() == headerV10Size {
  326. return true, nil
  327. }
  328. if info.Size() > headerV10Size {
  329. decSize, err := sio.DecryptedSize(uint64(info.Size() - headerV10Size))
  330. if err != nil {
  331. return false, err
  332. }
  333. if int64(decSize) == offset {
  334. return true, nil
  335. }
  336. }
  337. return false, nil
  338. }
  339. type encryptedFileHeader struct {
  340. version byte
  341. nonce []byte
  342. }
  343. func (h *encryptedFileHeader) Store(f *os.File) error {
  344. buf := make([]byte, 0, headerV10Size)
  345. buf = append(buf, version10)
  346. buf = append(buf, h.nonce...)
  347. _, err := f.Write(buf)
  348. return err
  349. }
  350. func (h *encryptedFileHeader) Load(f *os.File) error {
  351. header := make([]byte, 1+nonceV10Size)
  352. _, err := io.ReadFull(f, header)
  353. if err != nil {
  354. return err
  355. }
  356. h.version = header[0]
  357. if h.version == version10 {
  358. h.nonce = header[1:]
  359. return nil
  360. }
  361. return fmt.Errorf("unsupported encryption version: %v", h.version)
  362. }
  363. type cryptedFileWrapper struct {
  364. *os.File
  365. }
  366. func (w *cryptedFileWrapper) ReadAt(p []byte, offset int64) (n int, err error) {
  367. return w.File.ReadAt(p, offset+headerV10Size)
  368. }