cryptfs.go 7.9 KB

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