cryptfs.go 9.9 KB

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