cryptfs.go 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395
  1. // Copyright (C) 2019-2023 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, *pipeat.PipeReaderAt, 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. 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, r, 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 := pipeat.PipeInDir(fs.localTempDir)
  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) ([]os.FileInfo, error) {
  207. f, err := os.Open(dirname)
  208. if err != nil {
  209. return nil, err
  210. }
  211. list, err := f.Readdir(-1)
  212. f.Close()
  213. if err != nil {
  214. return nil, err
  215. }
  216. result := make([]os.FileInfo, 0, len(list))
  217. for _, info := range list {
  218. result = append(result, fs.ConvertFileInfo(info))
  219. }
  220. return result, nil
  221. }
  222. // IsUploadResumeSupported returns false sio does not support random access writes
  223. func (*CryptFs) IsUploadResumeSupported() 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. if !info.Mode().IsRegular() {
  262. return info
  263. }
  264. size := info.Size()
  265. if size >= headerV10Size {
  266. size -= headerV10Size
  267. decryptedSize, err := sio.DecryptedSize(uint64(size))
  268. if err == nil {
  269. size = int64(decryptedSize)
  270. }
  271. } else {
  272. size = 0
  273. }
  274. return NewFileInfo(info.Name(), info.IsDir(), size, info.ModTime(), false)
  275. }
  276. func (fs *CryptFs) getFileAndEncryptionKey(name string) (*os.File, [32]byte, error) {
  277. var key [32]byte
  278. f, err := os.Open(name)
  279. if err != nil {
  280. return nil, key, err
  281. }
  282. header := encryptedFileHeader{}
  283. err = header.Load(f)
  284. if err != nil {
  285. f.Close()
  286. return nil, key, err
  287. }
  288. kdf := hkdf.New(sha256.New, fs.masterKey, header.nonce, nil)
  289. _, err = io.ReadFull(kdf, key[:])
  290. if err != nil {
  291. f.Close()
  292. return nil, key, err
  293. }
  294. return f, key, err
  295. }
  296. func (*CryptFs) encryptWrapper(dst io.Writer, src io.Reader, config sio.Config) (int64, error) {
  297. encReader, err := sio.EncryptReader(src, config)
  298. if err != nil {
  299. return 0, err
  300. }
  301. return doCopy(dst, encReader, make([]byte, 65568))
  302. }
  303. func (fs *CryptFs) decryptWrapper(dst io.Writer, src io.Reader, config sio.Config) (int64, error) {
  304. if fs.readBufferSize <= 0 {
  305. return sio.Decrypt(dst, src, config)
  306. }
  307. br := bufio.NewReaderSize(src, fs.readBufferSize)
  308. decReader, err := sio.DecryptReader(br, config)
  309. if err != nil {
  310. return 0, err
  311. }
  312. return doCopy(dst, decReader, make([]byte, 65568))
  313. }
  314. func isZeroBytesDownload(f *os.File, offset int64) (bool, error) {
  315. info, err := f.Stat()
  316. if err != nil {
  317. return false, err
  318. }
  319. if info.Size() == headerV10Size {
  320. return true, nil
  321. }
  322. if info.Size() > headerV10Size {
  323. decSize, err := sio.DecryptedSize(uint64(info.Size() - headerV10Size))
  324. if err != nil {
  325. return false, err
  326. }
  327. if int64(decSize) == offset {
  328. return true, nil
  329. }
  330. }
  331. return false, nil
  332. }
  333. type encryptedFileHeader struct {
  334. version byte
  335. nonce []byte
  336. }
  337. func (h *encryptedFileHeader) Store(f *os.File) error {
  338. buf := make([]byte, 0, headerV10Size)
  339. buf = append(buf, version10)
  340. buf = append(buf, h.nonce...)
  341. _, err := f.Write(buf)
  342. return err
  343. }
  344. func (h *encryptedFileHeader) Load(f *os.File) error {
  345. header := make([]byte, 1+nonceV10Size)
  346. _, err := io.ReadFull(f, header)
  347. if err != nil {
  348. return err
  349. }
  350. h.version = header[0]
  351. if h.version == version10 {
  352. h.nonce = header[1:]
  353. return nil
  354. }
  355. return fmt.Errorf("unsupported encryption version: %v", h.version)
  356. }
  357. type cryptedFileWrapper struct {
  358. *os.File
  359. }
  360. func (w *cryptedFileWrapper) ReadAt(p []byte, offset int64) (n int, err error) {
  361. return w.File.ReadAt(p, offset+headerV10Size)
  362. }