/* * Copyright (C) 2010-2012, Gaetan Bisson . * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * StirFS. The secure, transparent and irresistible filesystem. * * StirFS is an encrypted filesystem for FUSE; its design focuses are * minimalism, flexibility, and security. Minimalism should be obvious at the * view of this single, short 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 -lfuse -lcrypto -o stirfs stirfs.c */ /* * INTERNALS 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, stir_encblock() uses a novel mode of operation which is * more secure than CTR but preserves its flexibility at the byte level. * * We use the uintptr_t type for efficient counter arithmetic on both 32- and * 64-bit platforms; as a drawback, bits after the first 2^32 are encrypted * differently on each architecture. */ /* **************************************************************************** * * HEADERS * */ #define _XOPEN_SOURCE 700 #define _FILE_OFFSET_BITS 64 #include #include #include #include #include #define DTTOIF(dirtype) ((dirtype)<<12) #include #include #include #define FUSE_USE_VERSION 26 #define PATH_MAX 4096 #include #include #include /* **************************************************************************** * * STIRFS INTERNAL STRUCTURES * */ /* FUSE context data */ struct stir_ctx { char *root; /* root backend directory */ AES_KEY *enc; /* AES encryption key */ AES_KEY *dec; /* AES decryption key */ }; /* Access the FUSE context data */ #define CTX ((struct stir_ctx *)fuse_get_context()->private_data) /* FUSE file handler data */ struct stir_fh { intptr_t fd; /* backend file descriptor */ char *nonce; /* file nonce or NULL */ }; /* Access the FUSE file handler data */ #define FH ((struct stir_fh *)(intptr_t)fi->fh) /* Create the FUSE file handler data */ #define MKFH { fi->fh = (intptr_t)malloc(sizeof(struct stir_fh)); } /* **************************************************************************** * * STIRFS INTERNAL PRIMITIVES * */ /* Helper for base64 encoding */ const char stir_encode_aux[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789,-"; /* Helper for base64 decoding */ const int stir_decode_aux[] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 62, 63, -1, -1, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, }; /* Base64 encode n characters of buf into str */ void stir_encode (char *str, const unsigned char *buf, const int n) { int i, j, x; for (i=0,j=0; i>18)&63]; str[j+1] = stir_encode_aux[(x>>12)&63]; str[j+2] = (i+1> 6)&63] : '+'; str[j+3] = (i+2> 0)&63] : '+'; } str[j] = 0; } /* Base64 decode str into n characters of buf */ void stir_decode (unsigned char *buf, const char *str, int *n) { int i, j, x; for (i=0,j=0; str[j]; i+=3,j+=4) { x = stir_decode_aux[str[j+0]]<<18; x += stir_decode_aux[str[j+1]]<<12; x += stir_decode_aux[str[j+2]]<< 6; x += stir_decode_aux[str[j+3]]<< 0; buf[i+0] = (x>>16)&255; buf[i+1] = (x>> 8)&255; buf[i+2] = (x>> 0)&255; } *n = i - (str[j-2]=='+' ? 2 : str[j-1]=='+' ? 1 : 0); } /* Xor block Y into X */ #define BLOCK_XOR(X,Y) { \ int i; \ for (i=0; ienc); } return n; } /* Decrypt l characters of buf in CBC mode */ void stir_decbc (unsigned char *buf, const int l) { int n; for (n=l-AES_BLOCK_SIZE; n>=0; n-=AES_BLOCK_SIZE) { AES_decrypt(buf+n, buf+n, CTX->dec); if (n) BLOCK_XOR(buf+n, buf+n-AES_BLOCK_SIZE); } } /* Encrypt basename dec into enc */ void stir_encbase (char *enc, const char *dec) { int n; char tmp[PATH_MAX]; if (!strcmp(dec,"") || !strcmp(dec,".") || !strcmp(dec,"..")) { strcpy(enc, dec); return; } strcpy(tmp, dec); n = stir_encbc((unsigned char *)tmp, strlen(dec)+1); if (n>AES_BLOCK_SIZE) BLOCK_XOR(tmp, tmp+n-AES_BLOCK_SIZE); stir_encbc((unsigned char *)tmp, n); stir_encode(enc, (unsigned char *)tmp, n); } /* Decrypt basename enc into dec */ void stir_decbase (char *dec, const char *enc) { int n; if (!strcmp(enc,"") || !strcmp(enc,".") || !strcmp(enc,"..")) { strcpy(dec, enc); return; } stir_decode((unsigned char *)dec, enc, &n); stir_decbc((unsigned char *)dec, n); if (n>AES_BLOCK_SIZE) BLOCK_XOR(dec, dec+n-AES_BLOCK_SIZE); stir_decbc((unsigned char *)dec ,n); } /* Encrypt path dec into enc */ static void stir_encpath (char *enc, const char *dec) { char *h, *t, p[PATH_MAX]; strcpy(p, dec); strcpy(enc, ""); h = p; t = p; do if (*t=='/') { *t = 0; stir_encbase(enc+strlen(enc), h); strcat(enc, "/"); h = t+1; } while (*(++t)); stir_encbase(enc+strlen(enc), h); } /* Decrypt path enc into dec */ static void stir_decpath (char *dec, const char *enc) { char *h, *t, p[PATH_MAX]; strcpy(p, enc); strcpy(dec, ""); h = p; t = p; do if (*t=='/') { *t = 0; stir_decbase(dec+strlen(dec), h); strcat(dec, "/"); h = t+1; } while (*(++t)); stir_decbase(dec+strlen(dec), h); } /* Encrypt path dec into absolute path enc */ static void stir_abspath (char *enc, const char *dec) { strcpy(enc, CTX->root); strcat(enc, "/"); stir_encpath(enc+strlen(CTX->root)+1, dec); } /* Encrypt count characters of buf, using off and nonce */ void stir_encblock (char *buf, size_t count, off_t off, char *nonce) { uintptr_t c, d; unsigned char ivc[AES_BLOCK_SIZE], mask[2*AES_BLOCK_SIZE], *e; memcpy(ivc, nonce, AES_BLOCK_SIZE); *(uintptr_t *)ivc += off/AES_BLOCK_SIZE; for (c=0,d=off; cenc); memcpy(mask+AES_BLOCK_SIZE, mask, AES_BLOCK_SIZE); e = mask+(d%AES_BLOCK_SIZE); (*(uintptr_t *)ivc)++; } buf[c] = ( ( ( ( ( ( ( ( ( ( ( ( buf[c] ) ^ e[2] ) + e[3] ) ^ e[5] ) + e[7] ) ^ e[11] ) + e[13] ) ^ e[1] ) + e[4] ) ^ e[6] ) + e[9] ) ^ e[10] ) + e[14] ; } } /* Decrypt count characters of buf, using off and nonce */ void stir_decblock (char *buf, size_t count, off_t off, char *nonce) { uintptr_t c, d; unsigned char ivc[AES_BLOCK_SIZE], mask[2*AES_BLOCK_SIZE], *e; memcpy(ivc, nonce, AES_BLOCK_SIZE); *(uintptr_t *)ivc += off/AES_BLOCK_SIZE; for (c=0,d=off; cenc); memcpy(mask+AES_BLOCK_SIZE, mask, AES_BLOCK_SIZE); e = mask+(d%AES_BLOCK_SIZE); (*(uintptr_t *)ivc)++; } buf[c] = ( ( ( ( ( ( ( ( ( ( ( ( buf[c] ) - e[14] ) ^ e[10] ) - e[9] ) ^ e[6] ) - e[4] ) ^ e[1] ) - e[13] ) ^ e[11] ) - e[7] ) ^ e[5] ) - e[3] ) ^ e[2] ; } } /* **************************************************************************** * * SITRFS FUSE PRIMITIVES * * See `struct fuse_operations` from: /usr/include/fuse/fuse.h * */ /* Get file attributes */ int stir_getattr (const char *path, struct stat *buf) { int r; char abs[PATH_MAX]; stir_abspath(abs, path); r = lstat(abs, buf); if (r) return -errno; if (S_ISREG(buf->st_mode)) buf->st_size -= AES_BLOCK_SIZE; return 0; } /* Read the target of a symbolic link */ int stir_readlink (const char *path, char *buf, size_t size) { int r; char enc[PATH_MAX]; if (sizeflags&O_WRONLY) fi->flags ^= O_WRONLY|O_RDWR; fd = open(abs, fi->flags); if (fd<0) return -errno; MKFH; FH->fd = fd; fstat(fd, &buf); if (S_ISREG(buf.st_mode)) { FH->nonce = malloc(AES_BLOCK_SIZE); r = pread(fd, FH->nonce, AES_BLOCK_SIZE, 0); if (rnonce = NULL; return 0; } /* Read data from an open file */ int stir_read (const char *path, char *buf, size_t count, off_t off, struct fuse_file_info *fi) { int r; r = pread(FH->fd, buf, count, off+AES_BLOCK_SIZE); if (r<0) return -errno; if (FH->nonce) stir_decblock(buf, count, off, FH->nonce); 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) { int r; char *enc; enc = malloc(count); memcpy(enc, buf, count); if (FH->nonce) stir_encblock(enc, count, off, FH->nonce); r = pwrite(FH->fd, enc, count, off+AES_BLOCK_SIZE); free(enc); if (r<0) return -errno; return r; } /* Get file system statistics */ int stir_statfs (const char *path, struct statvfs *stat) { int r; char abs[PATH_MAX]; stir_abspath(abs, path); r = statvfs(abs, stat); if (r) return -errno; return 0; } /* Possibly flush cached data */ int stir_flush (const char *path, struct fuse_file_info *fi) { return 0; } /* Release an open file */ int stir_release (const char *path, struct fuse_file_info *fi) { int r; r = close(FH->fd); if (r) return -errno; free(FH->nonce); free(FH); return 0; } /* Synchronize file contents */ int stir_fsync (const char *path, int data, struct fuse_file_info *fi) { int r; r = data ? fdatasync(FH->fd) : fsync(FH->fd); 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) { int r; char abs[PATH_MAX]; stir_abspath(abs, path); r = lsetxattr(abs, 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) { int r; char abs[PATH_MAX]; stir_abspath(abs, path); r = lgetxattr(abs, name, value, size); if (r<0) return -errno; return r; } /* List extended attributes */ int stir_listxattr (const char *path, char *list, size_t size) { int r; char abs[PATH_MAX]; stir_abspath(abs, path); r = llistxattr(abs, list, size); if (r<0) return -errno; return r; } /* Remove extended attributes */ int stir_removexattr (const char *path, const char *name) { int r; char abs[PATH_MAX]; stir_abspath(abs, path); r = lremovexattr(abs, name); if (r) return -errno; return 0; } /* Open directory */ int stir_opendir (const char *path, struct fuse_file_info *fi) { DIR *dp; char abs[PATH_MAX]; stir_abspath(abs, path); dp = opendir(abs); if (!dp) return -errno; MKFH; FH->fd = (intptr_t)dp; return 0; } /* Read directory */ int stir_readdir (const char *path, void *entry, fuse_fill_dir_t fill, off_t off, struct fuse_file_info *fi) { DIR *dp; struct stat buf; struct dirent *de; char name[PATH_MAX]; dp = (DIR *)FH->fd; seekdir(dp, off); while ((de=readdir(dp))) { stir_decbase(name, de->d_name); memset(&buf, 0, sizeof(buf)); buf.st_ino = de->d_ino; buf.st_mode = DTTOIF(de->d_type); if (fill(entry,name,&buf,telldir(dp))) break; } return 0; } /* Release directory */ int stir_releasedir (const char *path, struct fuse_file_info *fi) { int r; r = closedir((DIR *)FH->fd); if (r) return -errno; free(FH); return 0; } /* Synchronize directory contents */ int stir_fsyncdir (const char *path, int data, struct fuse_file_info *fi) { int r; r = data ? fdatasync(FH->fd) : fsync(FH->fd); if (r) return -errno; return 0; } /* Initialize filesystem */ void *stir_init (struct fuse_conn_info *conn) { return CTX; } /* Clean up filesystem */ void stir_destroy (void *ctx) { free(CTX->root); memset(CTX->enc, 0, sizeof(AES_KEY)); memset(CTX->dec, 0, sizeof(AES_KEY)); free(CTX->enc); free(CTX->dec); free(ctx); } /* Check file access permissions */ int stir_access (const char *path, int mode) { int r; char abs[PATH_MAX]; stir_abspath(abs, path); r = access(abs, mode); if (r) return -errno; return 0; } /* Create and open a file */ int stir_create (const char *path, mode_t mode, struct fuse_file_info *fi) { int fd, r, rnd[4]; char abs[PATH_MAX]; stir_abspath(abs, path); fd = open(abs, fi->flags, mode); if (fd<0) return -errno; MKFH; FH->fd = fd; if (S_ISREG(mode)) { FH->nonce = malloc(SHA256_DIGEST_LENGTH); rnd[0]=time(NULL); rnd[1]=rand(); rnd[2]=clock(); rnd[3]=rand(); SHA256((unsigned char *)rnd, sizeof(rnd), (unsigned char *)FH->nonce); r = pwrite(fd, FH->nonce, AES_BLOCK_SIZE, 0); if (rnonce = NULL; 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; r = ftruncate(FH->fd, off+AES_BLOCK_SIZE); if (r) return -errno; return 0; } /* Get attributes from an open file */ int stir_fgetattr (const char *path, struct stat *buf, struct fuse_file_info *fi) { int r; r = fstat(FH->fd, buf); if (r) return -errno; if (S_ISREG(buf->st_mode)) buf->st_size -= AES_BLOCK_SIZE; 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]) { int r; char abs[PATH_MAX]; stir_abspath(abs, path); r = utimensat(-1, abs, times, 0); if (r) return -errno; return 0; } /* **************************************************************************** * * PUTTING IT ALL TOGETHER * */ /* FUSE file system operation structure */ struct fuse_operations stir_op = { stir_getattr, stir_readlink, NULL, /* getdir, deprecated by readdir */ stir_mknod, stir_mkdir, stir_unlink, stir_rmdir, stir_symlink, stir_rename, stir_link, stir_chmod, stir_chown, stir_truncate, NULL, /* utime, deprecated by utimens */ stir_open, stir_read, stir_write, stir_statfs, stir_flush, stir_release, stir_fsync, stir_setxattr, stir_getxattr, stir_listxattr, stir_removexattr, stir_opendir, stir_readdir, stir_releasedir, stir_fsyncdir, stir_init, stir_destroy, stir_access, stir_create, stir_ftruncate, stir_fgetattr, NULL, /* lock, not implemented */ stir_utimens, NULL, /* bmap, not implemented */ 1, /* flag_nullpath_ok */ 1, /* flag_nopath */ 1, /* flag_utime_omit_ok */ 0, /* flag_reserved */ NULL, /* ioctl, not implemented */ NULL, /* poll, not implemented */ NULL, /* write_buf, not implemented */ NULL, /* read_buf, not implemented */ NULL, /* flock, not implemented */ }; /* Derive keys from password */ void stir_keys (AES_KEY *enc, AES_KEY *dec) { char passwd[PATH_MAX]; struct termios term, pass; unsigned char hash[SHA256_DIGEST_LENGTH]; tcgetattr(0, &term); pass = term; pass.c_lflag &= ~ECHO; tcsetattr(0, TCSANOW, &pass); printf("Password: "); while (!fgets(passwd, PATH_MAX, stdin)); passwd[strlen(passwd)-1]=0; printf("\n"); tcsetattr(0, TCSANOW, &term); strcat(passwd, "{StirFS}{StirFS}"); SHA256((unsigned char *)passwd, strlen(passwd)+1, hash); AES_set_encrypt_key(hash, 256, enc); AES_set_decrypt_key(hash, 256, dec); memset(passwd, 0, PATH_MAX); } /* Run FUSE */ int main (int argc, char *argv[]) { struct stir_ctx *ctx; if (argc<3) { char *argh[] = { "stirfs rootdir", "-h" }; printf("\ StirFS. The secure, transparent and irresistible filesystem.\n\ Copyright (C) 2010-2012 Gaetan Bisson. All rights reserved.\n\ Version 1.3; compiled on "__DATE__".\n\ \n\ Mounts an encrypted filesystem in mountpoint using rootdir as backend.\n\ \n\ "); return fuse_main(2, argh, &stir_op, NULL); } ctx = malloc(sizeof(struct stir_ctx)); ctx->enc = malloc(sizeof(AES_KEY)); ctx->dec = malloc(sizeof(AES_KEY)); ctx->root = realpath(argv[1], NULL); stir_keys(ctx->enc, ctx->dec); argv[1] = "stirfs"; return fuse_main(argc-1, argv+1, &stir_op, ctx); }