cryptfs.go 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  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. "bytes"
  17. "crypto/rand"
  18. "crypto/sha256"
  19. "fmt"
  20. "io"
  21. "net/http"
  22. "os"
  23. "github.com/eikenb/pipeat"
  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. },
  56. masterKey: []byte(config.Passphrase.GetPayload()),
  57. }
  58. if tempPath == "" {
  59. fs.localTempDir = rootDir
  60. } else {
  61. fs.localTempDir = tempPath
  62. }
  63. return fs, nil
  64. }
  65. // Name returns the name for the Fs implementation
  66. func (fs *CryptFs) Name() string {
  67. return fs.name
  68. }
  69. // Open opens the named file for reading
  70. func (fs *CryptFs) Open(name string, offset int64) (File, *pipeat.PipeReaderAt, func(), error) {
  71. f, key, err := fs.getFileAndEncryptionKey(name)
  72. if err != nil {
  73. return nil, nil, nil, err
  74. }
  75. isZeroDownload, err := isZeroBytesDownload(f, offset)
  76. if err != nil {
  77. f.Close()
  78. return nil, nil, nil, err
  79. }
  80. r, w, err := pipeat.PipeInDir(fs.localTempDir)
  81. if err != nil {
  82. f.Close()
  83. return nil, nil, nil, err
  84. }
  85. go func() {
  86. if isZeroDownload {
  87. w.CloseWithError(err) //nolint:errcheck
  88. f.Close()
  89. fsLog(fs, logger.LevelDebug, "zero bytes download completed, path: %q", name)
  90. return
  91. }
  92. var n int64
  93. var err error
  94. if offset == 0 {
  95. n, err = sio.Decrypt(w, f, fs.getSIOConfig(key))
  96. } else {
  97. var readerAt io.ReaderAt
  98. var readed, written int
  99. buf := make([]byte, 65536)
  100. wrapper := &cryptedFileWrapper{
  101. File: f,
  102. }
  103. readerAt, err = sio.DecryptReaderAt(wrapper, fs.getSIOConfig(key))
  104. if err == nil {
  105. finished := false
  106. for !finished {
  107. readed, err = readerAt.ReadAt(buf, offset)
  108. offset += int64(readed)
  109. if err != nil && err != io.EOF {
  110. break
  111. }
  112. if err == io.EOF {
  113. finished = true
  114. err = nil
  115. }
  116. if readed > 0 {
  117. written, err = w.Write(buf[:readed])
  118. n += int64(written)
  119. if err != nil {
  120. if err == io.EOF {
  121. err = io.ErrUnexpectedEOF
  122. }
  123. break
  124. }
  125. if readed != written {
  126. err = io.ErrShortWrite
  127. break
  128. }
  129. }
  130. }
  131. }
  132. }
  133. w.CloseWithError(err) //nolint:errcheck
  134. f.Close()
  135. fsLog(fs, logger.LevelDebug, "download completed, path: %q size: %v, err: %v", name, n, err)
  136. }()
  137. return nil, r, nil, nil
  138. }
  139. // Create creates or opens the named file for writing
  140. func (fs *CryptFs) Create(name string, flag int) (File, *PipeWriter, func(), error) {
  141. var err error
  142. var f *os.File
  143. if flag == 0 {
  144. f, err = os.Create(name)
  145. } else {
  146. f, err = os.OpenFile(name, flag, 0666)
  147. }
  148. if err != nil {
  149. return nil, nil, nil, err
  150. }
  151. header := encryptedFileHeader{
  152. version: version10,
  153. nonce: make([]byte, 32),
  154. }
  155. _, err = io.ReadFull(rand.Reader, header.nonce)
  156. if err != nil {
  157. f.Close()
  158. return nil, nil, nil, err
  159. }
  160. var key [32]byte
  161. kdf := hkdf.New(sha256.New, fs.masterKey, header.nonce, nil)
  162. _, err = io.ReadFull(kdf, key[:])
  163. if err != nil {
  164. f.Close()
  165. return nil, nil, nil, err
  166. }
  167. r, w, err := pipeat.PipeInDir(fs.localTempDir)
  168. if err != nil {
  169. f.Close()
  170. return nil, nil, nil, err
  171. }
  172. err = header.Store(f)
  173. if err != nil {
  174. r.Close()
  175. w.Close()
  176. f.Close()
  177. return nil, nil, nil, err
  178. }
  179. p := NewPipeWriter(w)
  180. go func() {
  181. n, err := sio.Encrypt(f, r, fs.getSIOConfig(key))
  182. errClose := f.Close()
  183. if err == nil && errClose != nil {
  184. err = errClose
  185. }
  186. r.CloseWithError(err) //nolint:errcheck
  187. p.Done(err)
  188. fsLog(fs, logger.LevelDebug, "upload completed, path: %q, readed bytes: %v, err: %v", name, n, err)
  189. }()
  190. return nil, p, nil, nil
  191. }
  192. // Truncate changes the size of the named file
  193. func (*CryptFs) Truncate(_ string, _ int64) error {
  194. return ErrVfsUnsupported
  195. }
  196. // ReadDir reads the directory named by dirname and returns
  197. // a list of directory entries.
  198. func (fs *CryptFs) ReadDir(dirname string) ([]os.FileInfo, error) {
  199. f, err := os.Open(dirname)
  200. if err != nil {
  201. return nil, err
  202. }
  203. list, err := f.Readdir(-1)
  204. f.Close()
  205. if err != nil {
  206. return nil, err
  207. }
  208. result := make([]os.FileInfo, 0, len(list))
  209. for _, info := range list {
  210. result = append(result, fs.ConvertFileInfo(info))
  211. }
  212. return result, nil
  213. }
  214. // IsUploadResumeSupported returns false sio does not support random access writes
  215. func (*CryptFs) IsUploadResumeSupported() bool {
  216. return false
  217. }
  218. // GetMimeType returns the content type
  219. func (fs *CryptFs) GetMimeType(name string) (string, error) {
  220. f, key, err := fs.getFileAndEncryptionKey(name)
  221. if err != nil {
  222. return "", err
  223. }
  224. defer f.Close()
  225. readSize, err := sio.DecryptedSize(512)
  226. if err != nil {
  227. return "", err
  228. }
  229. buf := make([]byte, readSize)
  230. n, err := io.ReadFull(f, buf)
  231. if err != nil && err != io.EOF && err != io.ErrUnexpectedEOF {
  232. return "", err
  233. }
  234. decrypted := bytes.NewBuffer(nil)
  235. _, err = sio.Decrypt(decrypted, bytes.NewBuffer(buf[:n]), fs.getSIOConfig(key))
  236. if err != nil {
  237. return "", err
  238. }
  239. ctype := http.DetectContentType(decrypted.Bytes())
  240. // Rewind file.
  241. _, err = f.Seek(0, io.SeekStart)
  242. return ctype, err
  243. }
  244. func (fs *CryptFs) getSIOConfig(key [32]byte) sio.Config {
  245. return sio.Config{
  246. MinVersion: sio.Version20,
  247. MaxVersion: sio.Version20,
  248. Key: key[:],
  249. }
  250. }
  251. // ConvertFileInfo returns a FileInfo with the decrypted size
  252. func (fs *CryptFs) ConvertFileInfo(info os.FileInfo) os.FileInfo {
  253. if !info.Mode().IsRegular() {
  254. return info
  255. }
  256. size := info.Size()
  257. if size >= headerV10Size {
  258. size -= headerV10Size
  259. decryptedSize, err := sio.DecryptedSize(uint64(size))
  260. if err == nil {
  261. size = int64(decryptedSize)
  262. }
  263. } else {
  264. size = 0
  265. }
  266. return NewFileInfo(info.Name(), info.IsDir(), size, info.ModTime(), false)
  267. }
  268. func (fs *CryptFs) getFileAndEncryptionKey(name string) (*os.File, [32]byte, error) {
  269. var key [32]byte
  270. f, err := os.Open(name)
  271. if err != nil {
  272. return nil, key, err
  273. }
  274. header := encryptedFileHeader{}
  275. err = header.Load(f)
  276. if err != nil {
  277. f.Close()
  278. return nil, key, err
  279. }
  280. kdf := hkdf.New(sha256.New, fs.masterKey, header.nonce, nil)
  281. _, err = io.ReadFull(kdf, key[:])
  282. if err != nil {
  283. f.Close()
  284. return nil, key, err
  285. }
  286. return f, key, err
  287. }
  288. func isZeroBytesDownload(f *os.File, offset int64) (bool, error) {
  289. info, err := f.Stat()
  290. if err != nil {
  291. return false, err
  292. }
  293. if info.Size() == headerV10Size {
  294. return true, nil
  295. }
  296. if info.Size() > headerV10Size {
  297. decSize, err := sio.DecryptedSize(uint64(info.Size() - headerV10Size))
  298. if err != nil {
  299. return false, err
  300. }
  301. if int64(decSize) == offset {
  302. return true, nil
  303. }
  304. }
  305. return false, nil
  306. }
  307. type encryptedFileHeader struct {
  308. version byte
  309. nonce []byte
  310. }
  311. func (h *encryptedFileHeader) Store(f *os.File) error {
  312. buf := make([]byte, 0, headerV10Size)
  313. buf = append(buf, version10)
  314. buf = append(buf, h.nonce...)
  315. _, err := f.Write(buf)
  316. return err
  317. }
  318. func (h *encryptedFileHeader) Load(f *os.File) error {
  319. header := make([]byte, 1+nonceV10Size)
  320. _, err := io.ReadFull(f, header)
  321. if err != nil {
  322. return err
  323. }
  324. h.version = header[0]
  325. if h.version == version10 {
  326. h.nonce = header[1:]
  327. return nil
  328. }
  329. return fmt.Errorf("unsupported encryption version: %v", h.version)
  330. }
  331. type cryptedFileWrapper struct {
  332. *os.File
  333. }
  334. func (w *cryptedFileWrapper) ReadAt(p []byte, offset int64) (n int, err error) {
  335. return w.File.ReadAt(p, offset+headerV10Size)
  336. }