cryptfs.go 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
  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) (DirLister, error) {
  208. f, err := os.Open(dirname)
  209. if err != nil {
  210. if isInvalidNameError(err) {
  211. err = os.ErrNotExist
  212. }
  213. return nil, err
  214. }
  215. return &cryptFsDirLister{f}, nil
  216. }
  217. // IsUploadResumeSupported returns false sio does not support random access writes
  218. func (*CryptFs) IsUploadResumeSupported() bool {
  219. return false
  220. }
  221. // IsConditionalUploadResumeSupported returns if resuming uploads is supported
  222. // for the specified size
  223. func (*CryptFs) IsConditionalUploadResumeSupported(_ int64) bool {
  224. return false
  225. }
  226. // GetMimeType returns the content type
  227. func (fs *CryptFs) GetMimeType(name string) (string, error) {
  228. f, key, err := fs.getFileAndEncryptionKey(name)
  229. if err != nil {
  230. return "", err
  231. }
  232. defer f.Close()
  233. readSize, err := sio.DecryptedSize(512)
  234. if err != nil {
  235. return "", err
  236. }
  237. buf := make([]byte, readSize)
  238. n, err := io.ReadFull(f, buf)
  239. if err != nil && err != io.EOF && err != io.ErrUnexpectedEOF {
  240. return "", err
  241. }
  242. decrypted := bytes.NewBuffer(nil)
  243. _, err = sio.Decrypt(decrypted, bytes.NewBuffer(buf[:n]), fs.getSIOConfig(key))
  244. if err != nil {
  245. return "", err
  246. }
  247. ctype := http.DetectContentType(decrypted.Bytes())
  248. // Rewind file.
  249. _, err = f.Seek(0, io.SeekStart)
  250. return ctype, err
  251. }
  252. func (fs *CryptFs) getSIOConfig(key [32]byte) sio.Config {
  253. return sio.Config{
  254. MinVersion: sio.Version20,
  255. MaxVersion: sio.Version20,
  256. Key: key[:],
  257. }
  258. }
  259. // ConvertFileInfo returns a FileInfo with the decrypted size
  260. func (fs *CryptFs) ConvertFileInfo(info os.FileInfo) os.FileInfo {
  261. return convertCryptFsInfo(info)
  262. }
  263. func (fs *CryptFs) getFileAndEncryptionKey(name string) (*os.File, [32]byte, error) {
  264. var key [32]byte
  265. f, err := os.Open(name)
  266. if err != nil {
  267. return nil, key, err
  268. }
  269. header := encryptedFileHeader{}
  270. err = header.Load(f)
  271. if err != nil {
  272. f.Close()
  273. return nil, key, err
  274. }
  275. kdf := hkdf.New(sha256.New, fs.masterKey, header.nonce, nil)
  276. _, err = io.ReadFull(kdf, key[:])
  277. if err != nil {
  278. f.Close()
  279. return nil, key, err
  280. }
  281. return f, key, err
  282. }
  283. func (*CryptFs) encryptWrapper(dst io.Writer, src io.Reader, config sio.Config) (int64, error) {
  284. encReader, err := sio.EncryptReader(src, config)
  285. if err != nil {
  286. return 0, err
  287. }
  288. return doCopy(dst, encReader, make([]byte, 65568))
  289. }
  290. func (fs *CryptFs) decryptWrapper(dst io.Writer, src io.Reader, config sio.Config) (int64, error) {
  291. if fs.readBufferSize <= 0 {
  292. return sio.Decrypt(dst, src, config)
  293. }
  294. br := bufio.NewReaderSize(src, fs.readBufferSize)
  295. decReader, err := sio.DecryptReader(br, config)
  296. if err != nil {
  297. return 0, err
  298. }
  299. return doCopy(dst, decReader, make([]byte, 65568))
  300. }
  301. func isZeroBytesDownload(f *os.File, offset int64) (bool, error) {
  302. info, err := f.Stat()
  303. if err != nil {
  304. return false, err
  305. }
  306. if info.Size() == headerV10Size {
  307. return true, nil
  308. }
  309. if info.Size() > headerV10Size {
  310. decSize, err := sio.DecryptedSize(uint64(info.Size() - headerV10Size))
  311. if err != nil {
  312. return false, err
  313. }
  314. if int64(decSize) == offset {
  315. return true, nil
  316. }
  317. }
  318. return false, nil
  319. }
  320. func convertCryptFsInfo(info os.FileInfo) os.FileInfo {
  321. if !info.Mode().IsRegular() {
  322. return info
  323. }
  324. size := info.Size()
  325. if size >= headerV10Size {
  326. size -= headerV10Size
  327. decryptedSize, err := sio.DecryptedSize(uint64(size))
  328. if err == nil {
  329. size = int64(decryptedSize)
  330. }
  331. } else {
  332. size = 0
  333. }
  334. return NewFileInfo(info.Name(), info.IsDir(), size, info.ModTime(), false)
  335. }
  336. type encryptedFileHeader struct {
  337. version byte
  338. nonce []byte
  339. }
  340. func (h *encryptedFileHeader) Store(f *os.File) error {
  341. buf := make([]byte, 0, headerV10Size)
  342. buf = append(buf, version10)
  343. buf = append(buf, h.nonce...)
  344. _, err := f.Write(buf)
  345. return err
  346. }
  347. func (h *encryptedFileHeader) Load(f *os.File) error {
  348. header := make([]byte, 1+nonceV10Size)
  349. _, err := io.ReadFull(f, header)
  350. if err != nil {
  351. return err
  352. }
  353. h.version = header[0]
  354. if h.version == version10 {
  355. h.nonce = header[1:]
  356. return nil
  357. }
  358. return fmt.Errorf("unsupported encryption version: %v", h.version)
  359. }
  360. type cryptedFileWrapper struct {
  361. *os.File
  362. }
  363. func (w *cryptedFileWrapper) ReadAt(p []byte, offset int64) (n int, err error) {
  364. return w.File.ReadAt(p, offset+headerV10Size)
  365. }
  366. type cryptFsDirLister struct {
  367. f *os.File
  368. }
  369. func (l *cryptFsDirLister) Next(limit int) ([]os.FileInfo, error) {
  370. if limit <= 0 {
  371. return nil, errInvalidDirListerLimit
  372. }
  373. files, err := l.f.Readdir(limit)
  374. for idx := range files {
  375. files[idx] = convertCryptFsInfo(files[idx])
  376. }
  377. return files, err
  378. }
  379. func (l *cryptFsDirLister) Close() error {
  380. return l.f.Close()
  381. }