/* * Copyright (C) 2010-2012 Gaetan Bisson. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER "AS IS" AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO * EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * StirFS. The secure, transparent and irresistible filesystem. * * StirFS is an encrypted filesystem for FUSE on Linux; it is designed for * minimalism, flexibility, and security. Minimalism should be obvious at the * view of this single, small C file; flexibility means StirFS just encrypts * filenames and blocks, and relies on a host filesystem for the rest; security * stems from strong cryptographic primitives and modes of operation. * * Compile with: * * cc -O2 -D_FILE_OFFSET_BITS=64 -lfuse -lcrypto -o stirfs stirfs.c */ /* * DESIGN OVERVIEW * * An AES256 master key is derived from the input password by computing the * SHA256 digest of its concatenation with a fixed salt value. * * Filenames are encrypted as follows: pad them with just enough NULL bytes to * get a length divisible by the AES block size (128 bits), encrypt by AES256 * in CBC mode, then xor the last block into the first one, encrypt again (to * ensure two-way diffusion), and finally base64-encode. * * The first block of each file is a nonce; following blocks consist of actual * data (with offsets shifted) encrypted by AES256 in (modified) CTR mode. */ /* * DESIGN NOTES * * We have no use for the strn*() family of string functions since they are * impractical and irrelevant here from a security standpoint; from a stability * standpoint, if the user wants very long paths, let them increase PATH_MAX. * * PATH_MAX could be superseded by dynamic allocation, since FUSE now supports * arbitrarily-long pathnames, but it certainly wouldn't be as efficient. * * The CTR mode of operation is elegant, but leaks information when distinct * plain-texts (such as versions of the same file) are encrypted using the same * key and nonce. Here, we use a novel mode of operation which is more secure * than CTR but preserves its flexibility at the byte-level; see stir_cblock(). * * We use the uintptr_t type for counter arithmetic, efficient on both 32- and * 64-bit platforms; as a drawback, bits after the first 2^32 are encrypted * differently on each architecture. * * Uninitialized data should be presented as NULL bytes by stir_read(). Unless * FUSE takes care of this itself, this might cause problems with applications * that I don't know of. */ /* **************************************************************************** * * HEADERS * */ #include #include #include #include #include #include #include #define __USE_UNIX98 #include #define FUSE_USE_VERSION 26 #include #ifndef PATH_MAX #define PATH_MAX 4096 #endif #include #include // An uintptr_t object must fit within AES_BLOCK_SIZE bytes. char check[sizeof(uintptr_t)>AES_BLOCK_SIZE?-1:0]; /* **************************************************************************** * * STIRFS INTERNAL STRUCTURES * * Our FUSE context consists of the rootdir, encryption and decryption keys. * Our FUSE file handlers consist of a backend file descriptor and a nonce. * */ struct stir_ctx { char *root; AES_KEY *ekey; AES_KEY *dkey; }; #define CTX ((struct stir_ctx *)fuse_get_context()->private_data) struct stir_fh { uintptr_t fd; unsigned char *nonce; }; #define FH ((struct stir_fh *)(uintptr_t)fi->fh) #define MKFH \ struct stir_fh *fh = malloc(sizeof(struct stir_fh)); \ fi->fh = (uintptr_t)fh /* **************************************************************************** * * STIRFS INTERNAL PRIMITIVES * */ const static char stir_b64c[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789,-"; inline static int stir_b64d (unsigned char c) { return c>96 ? c-97 : c>64 ? c-39 : c>47 ? c+4 : c>43 ? c+18 : 0; } inline static void stir_encode (char *res, unsigned char *buf, int n) { int i,j,x; for (i=0,j=0; i>18)&63]; res[j+1] = stir_b64c[(x>>12)&63]; res[j+2] = stir_b64c[(x>>6)&63]; res[j+3] = stir_b64c[x&63]; if (i+2>n) res[j+2] = '+'; if (i+3>n) res[j+3] = '+'; } res[j] = 0; } inline static void stir_decode (unsigned char *res, char *buf, int *n) { int m = strlen(buf); *n = buf[m-2]=='+' ? 3*m/4-2 : buf[m-1]=='+' ? 3*m/4-1 : 3*m/4; int i,j,x; for (i=0,j=0; j>16)&255; if (i+1<*n) res[i+1] = (x>>8)&255; if (i+2<*n) res[i+2] = x&255; } } inline static int stir_cbc_encrypt (unsigned char *cbuf, unsigned char *buf, AES_KEY *key, int l) { memcpy(cbuf, buf, l); memset(cbuf+l, 0, AES_BLOCK_SIZE); int n=0, i; while (l>n) { if (n) for (i=0; in) { AES_decrypt(cbuf+n, buf+n, key); if (n) for (i=0; iekey, strlen(name)+1); if (n>AES_BLOCK_SIZE) for (i=0; iekey, n); stir_encode(cname, yname, n); } inline static void stir_name (char *name, char *cname) { if (!strcmp(cname,"") || !strcmp(cname,".") || !strcmp(cname,"..")) { strcpy(name, cname); return; } int i,n; unsigned char xname[PATH_MAX], yname[PATH_MAX]; stir_decode(yname, cname, &n); stir_cbc_decrypt(xname, yname, CTX->dkey, n); if (n>AES_BLOCK_SIZE) for (i=0; idkey, n); } inline static void stir_cpath (char *cpath, const char *path) { char rpath[PATH_MAX]; strcpy(rpath, path); strcpy(cpath, ""); char *tok, *rem=rpath; while ((tok=strsep(&rem,"/"))) { stir_cname(cpath+strlen(cpath), tok); strcat(cpath, "/"); } cpath[strlen(cpath)-1] = 0; } inline static void stir_path (char *path, const char *cpath) { char rpath[PATH_MAX]; strcpy(rpath, cpath); strcpy(path, ""); char *tok, *rem=rpath; while ((tok=strsep(&rem,"/"))) { stir_name(path+strlen(path), tok); strcat(path, "/"); } path[strlen(path)-1] = 0; } static void stir_root_cpath (char *rpath, const char *path) { strcpy(rpath, CTX->root); strcat(rpath, "/"); stir_cpath(rpath+strlen(rpath), path); } inline static void stir_cblock (unsigned char *cbuf, unsigned char *buf, size_t count, off_t off, AES_KEY *key, unsigned char *nonce) { unsigned char ivc[AES_BLOCK_SIZE], mask[2*AES_BLOCK_SIZE]; memcpy(ivc, nonce, AES_BLOCK_SIZE); *(uintptr_t *)ivc += off/AES_BLOCK_SIZE; uintptr_t c=0, d=off; unsigned char *e; while (cfd, buf); if (r) return -errno; if (S_ISREG(buf->st_mode)) buf->st_size -= AES_BLOCK_SIZE; return 0; } // Get file attributes int stir_getattr (const char *path, struct stat *buf) { char rpath[PATH_MAX]; stir_root_cpath(rpath, path); int r = lstat(rpath, buf); if (r) return -errno; if (S_ISREG(buf->st_mode)) buf->st_size -= AES_BLOCK_SIZE; return 0; } // Change the size of an open file int stir_ftruncate (const char *path, off_t off, struct fuse_file_info *fi) { int r = ftruncate(FH->fd, off+AES_BLOCK_SIZE); if (r) return -errno; return 0; } // Change the size of a file int stir_truncate (const char *path, off_t off) { char rpath[PATH_MAX]; stir_root_cpath(rpath, path); int r = truncate(rpath, off+AES_BLOCK_SIZE); if (r) return -errno; return 0; } // Check file access permissions int stir_access (const char *path, int mode) { char rpath[PATH_MAX]; stir_root_cpath(rpath, path); int r = access(rpath, mode); if (r) return -errno; return 0; } // Change the permission bits of a file int stir_chmod (const char *path, mode_t mode) { char rpath[PATH_MAX]; stir_root_cpath(rpath, path); int r = chmod(rpath, mode); if (r) return -errno; return 0; } // Change the owner and group of a file int stir_chown (const char *path, uid_t uid, gid_t gid) { char rpath[PATH_MAX]; stir_root_cpath(rpath, path); int r = chown(rpath, uid, gid); if (r) return -errno; return 0; } // Change the access and modification times of a file with nanosecond resolution int stir_utimens (const char *path, const struct timespec times[2]) { char rpath[PATH_MAX]; stir_root_cpath(rpath, path); int r = utimensat(-1, rpath, times, 0); if (r) return -errno; return 0; } // Set extended attributes int stir_setxattr (const char *path, const char *name, const char *value, size_t size, int flags) { char rpath[PATH_MAX]; stir_root_cpath(rpath, path); int r = lsetxattr(rpath, name, value, size, flags); if (r) return -errno; return 0; } // Get extended attributes int stir_getxattr (const char *path, const char *name, char *value, size_t size) { char rpath[PATH_MAX]; stir_root_cpath(rpath, path); int r = lgetxattr(rpath, name, value, size); if (r<0) return -errno; return r; } // List extended attributes int stir_listxattr (const char *path, char *list, size_t size) { char rpath[PATH_MAX]; stir_root_cpath(rpath, path); int r = llistxattr(rpath, list, size); if (r<0) return -errno; return r; } // Remove extended attributes int stir_removexattr (const char *path, const char *name) { char rpath[PATH_MAX]; stir_root_cpath(rpath, path); int r = lremovexattr(rpath, name); if (r) return -errno; return 0; } // Read the target of a symbolic link int stir_readlink (const char *head, char *path, size_t size) { char rhead[PATH_MAX], cpath[PATH_MAX]; stir_root_cpath(rhead, head); int r = readlink(rhead, cpath, PATH_MAX); if (r<0) return -errno; stir_path(path, cpath); return 0; } // Create a symbolic link int stir_symlink (const char *path, const char *head) { char cpath[PATH_MAX], rhead[PATH_MAX]; stir_cpath(cpath, path); stir_root_cpath(rhead, head); int r = symlink(cpath, rhead); if (r) return -errno; return 0; } // Rename a file int stir_rename (const char *path, const char *npath) { char rpath[PATH_MAX], rnpath[PATH_MAX]; stir_root_cpath(rpath, path); stir_root_cpath(rnpath, npath); int r = rename(rpath, rnpath); if (r) return -errno; return 0; } // Create a hard link to a file int stir_link (const char *path, const char *head) { char rpath[PATH_MAX], rhead[PATH_MAX]; stir_root_cpath(rpath, path); stir_root_cpath(rhead, head); int r = link(rpath, rhead); if (r) return -errno; return 0; } // Create a directory int stir_mkdir (const char *path, mode_t mode) { char rpath[PATH_MAX]; stir_root_cpath(rpath, path); int r = mkdir(rpath, mode); if (r) return -errno; return 0; } // Remove a file int stir_unlink (const char *path) { char rpath[PATH_MAX]; stir_root_cpath(rpath, path); int r = unlink(rpath); if (r) return -errno; return 0; } // Remove a directory int stir_rmdir (const char *path) { char rpath[PATH_MAX]; stir_root_cpath(rpath, path); int r = rmdir(rpath); if (r) return -errno; return 0; } // Open directory int stir_opendir (const char *path, struct fuse_file_info *fi) { char rpath[PATH_MAX]; stir_root_cpath(rpath, path); MKFH; DIR *dp = opendir(rpath); FH->fd = (uintptr_t)dp; if (!dp) return -errno; return 0; } // Read directory int stir_readdir (const char *path, void *buf, fuse_fill_dir_t fill, off_t off, struct fuse_file_info *fi) { DIR *dp = (DIR *)FH->fd; seekdir(dp, off); struct dirent *de; char name[PATH_MAX]; struct stat st; while ((de=readdir(dp))) { stir_name(name, de->d_name); memset(&st, 0, sizeof(st)); st.st_ino = de->d_ino; st.st_mode = DTTOIF(de->d_type); if (fill(buf,name,&st,telldir(dp))) break; } return 0; } // Release directory int stir_releasedir (const char *path, struct fuse_file_info *fi) { int r = closedir((DIR *)FH->fd); if (r) return -errno; free(FH); return 0; } // Release an open file int stir_release (const char *path, struct fuse_file_info *fi) { int r = close(FH->fd); if (r) return -errno; free(FH->nonce); free(FH); return 0; } // [FUSE operations structure reference] No creation (O_CREAT, O_EXCL) and by // default also no truncation (O_TRUNC) flags will be passed to open(). // File open operation int stir_open (const char *path, struct fuse_file_info *fi) { char rpath[PATH_MAX]; stir_root_cpath(rpath, path); MKFH; if (fi->flags&O_WRONLY) fi->flags ^= O_WRONLY|O_RDWR; int fd = open(rpath, fi->flags); FH->fd = fd; if (fd<0) return -errno; struct stat stbuf; fstat(fd, &stbuf); unsigned char *nonce; if (S_ISREG(stbuf.st_mode)) { nonce = malloc(AES_BLOCK_SIZE); int r = pread(fd, nonce, AES_BLOCK_SIZE, 0); if (rnonce = nonce; return 0; } // Create and open a file int stir_create (const char *path, mode_t mode, struct fuse_file_info *fi) { char rpath[PATH_MAX]; stir_root_cpath(rpath, path); MKFH; int fd = open(rpath, fi->flags, mode); FH->fd = fd; if (fd<0) return -errno; unsigned char *nonce; if (S_ISREG(mode)) { nonce = malloc(SHA256_DIGEST_LENGTH); int rnd[] = { time(NULL), rand(), clock(), rand() }; SHA256((unsigned char *)rnd, sizeof(rnd), nonce); int r = pwrite(fd, nonce, AES_BLOCK_SIZE, 0); if (rnonce = nonce; return 0; } // Create a file node int stir_mknod (const char *path, mode_t mode, dev_t dev) { char rpath[PATH_MAX]; stir_root_cpath(rpath, path); int r = mknod(rpath,mode,dev); if (r) return -errno; if (S_ISREG(mode)) { unsigned char nonce[SHA256_DIGEST_LENGTH]; int rnd[] = { time(NULL), rand(), clock(), rand() }; SHA256((unsigned char *)rnd, sizeof(rnd), nonce); int fr = open(rpath, O_WRONLY); int r = pwrite(fr, nonce, AES_BLOCK_SIZE, 0); if (rfd, cbuf, count, off+AES_BLOCK_SIZE); if (r<0) return -errno; if (!FH->nonce) memcpy(buf, cbuf, count); else stir_block((unsigned char *)buf, cbuf, count, off, CTX->ekey, FH->nonce); free(cbuf); return r; } // Write data to an open file int stir_write (const char *path, const char *buf, size_t count, off_t off, struct fuse_file_info *fi) { unsigned char *cbuf = malloc(count); if (!FH->nonce) memcpy(cbuf, buf, count); else stir_cblock(cbuf, (unsigned char *)buf, count, off, CTX->ekey, FH->nonce); int r = pwrite(FH->fd, cbuf, count, off+AES_BLOCK_SIZE); if (r<0) return -errno; free(cbuf); return r; } // Possibly flush cached data int stir_flush (const char *path, struct fuse_file_info *fi) { return 0; } // Synchronize file contents int stir_fsync (const char *path, int data, struct fuse_file_info *fi) { int r = data ? fdatasync(FH->fd) : fsync(FH->fd); if (r) return -errno; return 0; } // Synchronize directory contents int stir_fsyncdir (const char *path, int data, struct fuse_file_info *fi) { return 0; } // Initialize filesystem void *stir_init (struct fuse_conn_info *conn) { return CTX; } // Clean up filesystem void stir_destroy (void *stuff) { } /* **************************************************************************** * * PUTTING IT ALL TOGETHER * */ // Operation structure to be fed to FUSE struct fuse_operations stir_oper = { .access = stir_access, // .bmap = bmap, .chmod = stir_chmod, .chown = stir_chown, .create = stir_create, .destroy = stir_destroy, .fgetattr = stir_fgetattr, // .flag_nullpath_ok = flag_nullpath_ok, .flush = stir_flush, .fsync = stir_fsync, .fsyncdir = stir_fsyncdir, .ftruncate = stir_ftruncate, .getattr = stir_getattr, .getxattr = stir_getxattr, .init = stir_init, // .ioctl = ioctl, .link = stir_link, .listxattr = stir_listxattr, // .lock = lock, .mkdir = stir_mkdir, .mknod = stir_mknod, .open = stir_open, .opendir = stir_opendir, // .poll = poll, .read = stir_read, .readdir = stir_readdir, .readlink = stir_readlink, .release = stir_release, .releasedir = stir_releasedir, .removexattr = stir_removexattr, .rename = stir_rename, .rmdir = stir_rmdir, .setxattr = stir_setxattr, .statfs = stir_statfs, .symlink = stir_symlink, .truncate = stir_truncate, .unlink = stir_unlink, .utimens = stir_utimens, .write = stir_write, }; // Ask for password and run FUSE int main (int argc, char *argv[]) { if (argc<3) { printf("\ StirFS. The secure, transparent and irresistible filesystem.\n\ Copyright (C) 2010-2012 Gaetan Bisson. All rights reserved.\n\ Version 1.2; compiled on "__DATE__".\n\ \n\ Mounts an encrypted filesystem in mountpoint using rootdir as backend.\n\ \n\ "); char *argv[] = { "stirfs rootdir", "-h" }; return fuse_main(2, argv, NULL, NULL); } char *passwd = getpass("Password: "); char *iv = "{StirFS}{StirFS}"; int n = strlen(passwd); int m = strlen(iv)+n+1; char *salt = malloc(m); strcpy(salt, passwd); strcat(salt, iv); unsigned char hash[SHA256_DIGEST_LENGTH]; SHA256((unsigned char *)salt, m, hash); AES_KEY *ekey = malloc(sizeof(AES_KEY)); AES_KEY *dkey = malloc(sizeof(AES_KEY)); AES_set_encrypt_key(hash, 256, ekey); AES_set_decrypt_key(hash, 256, dkey); memset(passwd, 0, n); memset(salt, 0, m); free(passwd); free(salt); struct stir_ctx *ctx = malloc(sizeof(struct stir_ctx)); ctx->root = realpath(argv[1], NULL); ctx->ekey = ekey; ctx->dkey = dkey; argv[1] = "stirfs"; return fuse_main(argc-1, argv+1, &stir_oper, ctx); }