cryptfs.go 7.8 KB

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