diff mbox series

[1/1] f2fs-tools: Introduce metadata encryption support

Message ID 20201005074133.1958633-2-satyat@google.com
State New
Headers show
Series userspace support for F2FS metadata encryption | expand

Commit Message

Satya Tangirala Oct. 5, 2020, 7:41 a.m. UTC
Introduce native metadata encryption support for F2FS. All blocks
other than the super block are encrypted with the specified metadata
encryption key and algorithm. The data unit number for each block is its
block number in the filesystem.

This patch introduces two new options '-A' and '-M' for specifying metadata
crypt options. '-A' takes the desired metadata encryption algorithm as
argument. '-M' takes the linux key_serial of the metadata encryption key as
the argument. The keyring key provided must be of a key type that supports
reading the payload from userspace.

mkfs.f2fs takes both these arguments, and stores the encryption algorithm
in the superblock of the FS.

The rest of the programs only take '-M', and use the encryption algorithm
stored in the superblock of the FS.

Signed-off-by: Satya Tangirala <satyat@google.com>
---
 fsck/main.c                   |  47 ++++++-
 fsck/mount.c                  |  33 ++++-
 include/f2fs_fs.h             |  10 +-
 include/f2fs_metadata_crypt.h |  21 ++++
 lib/Makefile.am               |   4 +-
 lib/f2fs_metadata_crypt.c     | 226 ++++++++++++++++++++++++++++++++++
 lib/libf2fs_io.c              |  87 +++++++++++--
 mkfs/f2fs_format.c            |   5 +-
 mkfs/f2fs_format_main.c       |  33 ++++-
 9 files changed, 446 insertions(+), 20 deletions(-)
 create mode 100644 include/f2fs_metadata_crypt.h
 create mode 100644 lib/f2fs_metadata_crypt.c

Comments

Jaegeuk Kim Oct. 7, 2020, 7:42 p.m. UTC | #1
Hi Satya,

On 10/05, Satya Tangirala wrote:
> Introduce native metadata encryption support for F2FS. All blocks
> other than the super block are encrypted with the specified metadata
> encryption key and algorithm. The data unit number for each block is its
> block number in the filesystem.
> 
> This patch introduces two new options '-A' and '-M' for specifying metadata
> crypt options. '-A' takes the desired metadata encryption algorithm as
> argument. '-M' takes the linux key_serial of the metadata encryption key as
> the argument. The keyring key provided must be of a key type that supports
> reading the payload from userspace.

Could you please update manpages as well?

> 
> mkfs.f2fs takes both these arguments, and stores the encryption algorithm
> in the superblock of the FS.
> 
> The rest of the programs only take '-M', and use the encryption algorithm
> stored in the superblock of the FS.
> 
> Signed-off-by: Satya Tangirala <satyat@google.com>
> ---
>  fsck/main.c                   |  47 ++++++-
>  fsck/mount.c                  |  33 ++++-
>  include/f2fs_fs.h             |  10 +-
>  include/f2fs_metadata_crypt.h |  21 ++++
>  lib/Makefile.am               |   4 +-
>  lib/f2fs_metadata_crypt.c     | 226 ++++++++++++++++++++++++++++++++++
>  lib/libf2fs_io.c              |  87 +++++++++++--
>  mkfs/f2fs_format.c            |   5 +-
>  mkfs/f2fs_format_main.c       |  33 ++++-
>  9 files changed, 446 insertions(+), 20 deletions(-)
>  create mode 100644 include/f2fs_metadata_crypt.h
>  create mode 100644 lib/f2fs_metadata_crypt.c
> 
> diff --git a/fsck/main.c b/fsck/main.c
> index 32559f1..6a4d867 100644
> --- a/fsck/main.c
> +++ b/fsck/main.c
> @@ -26,6 +26,8 @@
>  #include <stdbool.h>
>  #include "quotaio.h"
>  
> +#include "f2fs_metadata_crypt.h"
> +
>  struct f2fs_fsck gfsck;
>  
>  #ifdef WITH_ANDROID
> @@ -62,6 +64,7 @@ void fsck_usage()
>  			" (default 0)\n");
>  	MSG(0, "  -m <max-hash-collision>  set max cache hash collision"
>  			" (default 16)\n");
> +	MSG(0, "  -M Metadata encryption key_serial in keyring\n");
>  	MSG(0, "  -C encoding[:flag1,flag2] Set options for enabling"
>  			" casefolding\n");
>  	MSG(0, "  -d debug level [default:0]\n");
> @@ -92,6 +95,7 @@ void dump_usage()
>  	MSG(0, "  -S sparse_mode\n");
>  	MSG(0, "  -a [SSA dump segno from #1~#2 (decimal), for all 0~-1]\n");
>  	MSG(0, "  -b blk_addr (in 4KB)\n");
> +	MSG(0, "  -M Metadata encryption key_serial in keyring\n");
>  	MSG(0, "  -V print the version number and exit\n");
>  
>  	exit(1);
> @@ -107,6 +111,7 @@ void defrag_usage()
>  	MSG(0, "  -l length [default:512 (2MB)]\n");
>  	MSG(0, "  -t target block address [default: main_blkaddr + 2MB]\n");
>  	MSG(0, "  -i set direction as shrink [default: expand]\n");
> +	MSG(0, "  -M Metadata encryption key_serial in keyring\n");
>  	MSG(0, "  -V print the version number and exit\n");
>  	exit(1);
>  }
> @@ -119,6 +124,7 @@ void resize_usage()
>  	MSG(0, "  -i extended node bitmap, node ratio is 20%% by default\n");
>  	MSG(0, "  -s safe resize (Does not resize metadata)");
>  	MSG(0, "  -t target sectors [default: device size]\n");
> +	MSG(0, "  -M Metadata encryption key_serial in keyring\n");
>  	MSG(0, "  -V print the version number and exit\n");
>  	exit(1);
>  }
> @@ -129,6 +135,7 @@ void sload_usage()
>  	MSG(0, "[options]:\n");
>  	MSG(0, "  -C fs_config\n");
>  	MSG(0, "  -f source directory [path of the source directory]\n");
> +	MSG(0, "  -M Metadata encryption key_serial in keyring\n");
>  	MSG(0, "  -p product out directory\n");
>  	MSG(0, "  -s file_contexts\n");
>  	MSG(0, "  -S sparse_mode\n");
> @@ -200,7 +207,7 @@ void f2fs_parse_options(int argc, char *argv[])
>  	}
>  
>  	if (!strcmp("fsck.f2fs", prog)) {
> -		const char *option_string = ":aC:c:m:d:fg:O:p:q:StyV";
> +		const char *option_string = ":aC:c:m:M:d:fg:O:p:q:StyV";
>  		int opt = 0, val;
>  		char *token;
>  		struct option long_opt[] = {
> @@ -243,6 +250,12 @@ void f2fs_parse_options(int argc, char *argv[])
>  				c.cache_config.max_hash_collision =
>  						atoi(optarg);
>  				break;
> +			case 'M':
> +				if (f2fs_metadata_process_key(optarg)) {
> +					MSG(0, "Error: Invalid metadata key\n");
> +					fsck_usage();
> +				}
> +				break;
>  			case 'g':
>  				if (!strcmp(optarg, "android"))
>  					c.defset = CONF_ANDROID;
> @@ -345,7 +358,7 @@ void f2fs_parse_options(int argc, char *argv[])
>  				break;
>  		}
>  	} else if (!strcmp("dump.f2fs", prog)) {
> -		const char *option_string = "d:i:n:s:Sa:b:V";
> +		const char *option_string = "d:i:n:s:Sa:b:M:V";
>  		static struct dump_option dump_opt = {
>  			.nid = 0,	/* default root ino */
>  			.start_nat = -1,
> @@ -413,6 +426,12 @@ void f2fs_parse_options(int argc, char *argv[])
>  					ret = sscanf(optarg, "%x",
>  							&dump_opt.blk_addr);
>  				break;
> +			case 'M':
> +				if (f2fs_metadata_process_key(optarg)) {
> +					MSG(0, "Error: Invalid metadata key\n");
> +					dump_usage();
> +				}
> +				break;
>  			case 'V':
>  				show_version(prog);
>  				exit(0);
> @@ -427,7 +446,7 @@ void f2fs_parse_options(int argc, char *argv[])
>  
>  		c.private = &dump_opt;
>  	} else if (!strcmp("defrag.f2fs", prog)) {
> -		const char *option_string = "d:s:Sl:t:iV";
> +		const char *option_string = "d:M:s:Sl:t:iV";
>  
>  		c.func = DEFRAG;
>  		while ((option = getopt(argc, argv, option_string)) != EOF) {
> @@ -473,6 +492,12 @@ void f2fs_parse_options(int argc, char *argv[])
>  			case 'i':
>  				c.defrag_shrink = 1;
>  				break;
> +			case 'M':
> +				if (f2fs_metadata_process_key(optarg)) {
> +					MSG(0, "Error: Invalid metadata key\n");
> +					defrag_usage();
> +				}
> +				break;
>  			case 'V':
>  				show_version(prog);
>  				exit(0);
> @@ -485,7 +510,7 @@ void f2fs_parse_options(int argc, char *argv[])
>  				break;
>  		}
>  	} else if (!strcmp("resize.f2fs", prog)) {
> -		const char *option_string = "d:st:iV";
> +		const char *option_string = "d:M:st:iV";
>  
>  		c.func = RESIZE;
>  		while ((option = getopt(argc, argv, option_string)) != EOF) {
> @@ -515,6 +540,12 @@ void f2fs_parse_options(int argc, char *argv[])
>  			case 'i':
>  				c.large_nat_bitmap = 1;
>  				break;
> +			case 'M':
> +				if (f2fs_metadata_process_key(optarg)) {
> +					MSG(0, "Error: Invalid metadata key\n");
> +					resize_usage();
> +				}
> +				break;
>  			case 'V':
>  				show_version(prog);
>  				exit(0);
> @@ -527,7 +558,7 @@ void f2fs_parse_options(int argc, char *argv[])
>  				break;
>  		}
>  	} else if (!strcmp("sload.f2fs", prog)) {
> -		const char *option_string = "C:d:f:p:s:St:T:V";
> +		const char *option_string = "C:d:f:M:p:s:St:T:V";
>  #ifdef HAVE_LIBSELINUX
>  		int max_nr_opt = (int)sizeof(c.seopt_file) /
>  			sizeof(c.seopt_file[0]);
> @@ -553,6 +584,12 @@ void f2fs_parse_options(int argc, char *argv[])
>  			case 'f':
>  				c.from_dir = absolute_path(optarg);
>  				break;
> +			case 'M':
> +				if (f2fs_metadata_process_key(optarg)) {
> +					MSG(0, "Error: Invalid metadata key\n");
> +					sload_usage();
> +				}
> +				break;
>  			case 'p':
>  				c.target_out_dir = absolute_path(optarg);
>  				break;
> diff --git a/fsck/mount.c b/fsck/mount.c
> index 8ebc5b0..7520a8a 100644
> --- a/fsck/mount.c
> +++ b/fsck/mount.c
> @@ -11,6 +11,7 @@
>  #include "fsck.h"
>  #include "node.h"
>  #include "xattr.h"
> +#include "f2fs_metadata_crypt.h"
>  #include <locale.h>
>  #include <stdbool.h>
>  #ifdef HAVE_LINUX_POSIX_ACL_H
> @@ -561,6 +562,10 @@ void print_sb_state(struct f2fs_super_block *sb)
>  	if (f & cpu_to_le32(F2FS_FEATURE_COMPRESSION)) {
>  		MSG(0, "%s", " compression");
>  	}
> +	if (sb->metadata_crypt_alg) {
> +		MSG(0, "%s", " metadata_crypt");
> +	}
> +
>  	MSG(0, "\n");
>  	MSG(0, "Info: superblock encrypt level = %d, salt = ",
>  					sb->encryption_level);
> @@ -686,7 +691,7 @@ void update_superblock(struct f2fs_super_block *sb, int sb_mask)
>  	memcpy(buf + F2FS_SUPER_OFFSET, sb, sizeof(*sb));
>  	for (addr = SB0_ADDR; addr < SB_MAX_ADDR; addr++) {
>  		if (SB_MASK(addr) & sb_mask) {
> -			ret = dev_write_block(buf, addr);
> +			ret = dev_write_block_unencrypted(buf, addr);
>  			ASSERT(ret >= 0);
>  		}
>  	}
> @@ -927,6 +932,24 @@ int sanity_check_raw_super(struct f2fs_super_block *sb, enum SB_ADDR sb_addr)
>  		return -1;
>  	}
>  
> +	/*
> +	 * Check that metadata encryption is enabled on superblock when metadata
> +	 * crypt key is specified
> +	 */
> +	if (get_sb(metadata_crypt_alg) && !c.metadata_crypt_key) {
> +		MSG(0, "\tFilesystem has metadata encryption, but we're missing the metadata encryption key.\n");
> +		return -1;
> +	}
> +
> +	/*
> +	 * Check that metadata encryption is disabled on superblock when metadata
> +	 * crypt key is not specified
> +	 */
> +	if (!get_sb(metadata_crypt_alg) && c.metadata_crypt_key) {
> +		MSG(0, "\tFilesystem has does not have metadata encryption, but a metadata encryption key was specified.\n");
> +		return -1;
> +	}
> +
>  	if (sanity_check_area_boundary(sb, sb_addr))
>  		return -1;
>  	return 0;
> @@ -940,7 +963,7 @@ int validate_super_block(struct f2fs_sb_info *sbi, enum SB_ADDR sb_addr)
>  	if (!sbi->raw_super)
>  		return -ENOMEM;
>  
> -	if (dev_read_block(buf, sb_addr))
> +	if (dev_read_block_unencrypted(buf, sb_addr))
>  		return -1;
>  
>  	memcpy(sbi->raw_super, buf + F2FS_SUPER_OFFSET,
> @@ -3499,6 +3522,12 @@ int f2fs_do_mount(struct f2fs_sb_info *sbi)
>  	}
>  	sb = F2FS_RAW_SUPER(sbi);
>  
> +	/* Get metadata encryption algorithm */
> +	c.metadata_crypt_alg = get_sb(metadata_crypt_alg);
> +
> +	if (f2fs_metadata_verify_args())
> +		exit(1);
> +
>  	ret = check_sector_size(sb);
>  	if (ret)
>  		return -1;
> diff --git a/include/f2fs_fs.h b/include/f2fs_fs.h
> index b5bda13..6b1912d 100644
> --- a/include/f2fs_fs.h
> +++ b/include/f2fs_fs.h
> @@ -441,6 +441,11 @@ struct f2fs_configuration {
>  
>  	/* cache parameters */
>  	dev_cache_config_t cache_config;
> +
> +	/* metadata encryption */
> +	__u8 *metadata_crypt_key;
> +	int metadata_crypt_key_len;
> +	int metadata_crypt_alg;
>  };
>  
>  #ifdef CONFIG_64BIT
> @@ -675,7 +680,8 @@ struct f2fs_super_block {
>  	__u8 hot_ext_count;		/* # of hot file extension */
>  	__le16  s_encoding;		/* Filename charset encoding */
>  	__le16  s_encoding_flags;	/* Filename charset encoding flags */
> -	__u8 reserved[306];		/* valid reserved region */
> +	__le32	metadata_crypt_alg;	/* The metadata encryption algorithm (FSCRYPT_MODE_*) */
> +	__u8 reserved[302];		/* valid reserved region */
>  	__le32 crc;			/* checksum of superblock */
>  } __attribute__((packed));
>  
> @@ -1237,12 +1243,14 @@ extern int dev_readahead(__u64, size_t UNUSED(len));
>  #endif
>  extern int dev_write(void *, __u64, size_t);
>  extern int dev_write_block(void *, __u64);
> +extern int dev_write_block_unencrypted(void *, __u64);
>  extern int dev_write_dump(void *, __u64, size_t);
>  /* All bytes in the buffer must be 0 use dev_fill(). */
>  extern int dev_fill(void *, __u64, size_t);
>  extern int dev_fill_block(void *, __u64);
>  
>  extern int dev_read_block(void *, __u64);
> +extern int dev_read_block_unencrypted(void *, __u64);
>  extern int dev_reada_block(__u64);
>  
>  extern int dev_read_version(void *, __u64, size_t);
> diff --git a/include/f2fs_metadata_crypt.h b/include/f2fs_metadata_crypt.h
> new file mode 100644
> index 0000000..d15873d
> --- /dev/null
> +++ b/include/f2fs_metadata_crypt.h
> @@ -0,0 +1,21 @@
> +/**
> + * f2fs_metadata_crypt.h
> + *
> + * Copyright (c) 2020 Google LLC
> + *
> + * Dual licensed under the GPL or LGPL version 2 licenses.
> + */
> +
> +#include <inttypes.h>
> +#include <linux/fscrypt.h>
> +
> +int f2fs_get_crypt_alg(const char *optarg);
> +
> +void f2fs_print_crypt_algs(void);
> +
> +int f2fs_metadata_process_key(const char *key_serial);
> +
> +int f2fs_metadata_verify_args(void);
> +
> +void *f2fs_metadata_crypt_blocks(void *src_buf, size_t len, __u64 blk_addr,
> +	bool encrypt);
> diff --git a/lib/Makefile.am b/lib/Makefile.am
> index 871d773..a82d753 100644
> --- a/lib/Makefile.am
> +++ b/lib/Makefile.am
> @@ -2,10 +2,10 @@
>  
>  lib_LTLIBRARIES = libf2fs.la
>  
> -libf2fs_la_SOURCES = libf2fs.c libf2fs_io.c libf2fs_zoned.c nls_utf8.c
> +libf2fs_la_SOURCES = libf2fs.c libf2fs_io.c libf2fs_zoned.c nls_utf8.c f2fs_metadata_crypt.c
>  libf2fs_la_CFLAGS = -Wall
>  libf2fs_la_CPPFLAGS = -I$(top_srcdir)/include
> -libf2fs_la_LDFLAGS = -version-info $(LIBF2FS_CURRENT):$(LIBF2FS_REVISION):$(LIBF2FS_AGE)
> +libf2fs_la_LDFLAGS = -lkeyutils -version-info $(LIBF2FS_CURRENT):$(LIBF2FS_REVISION):$(LIBF2FS_AGE)
>  
>  root_libdir=@root_libdir@
>  
> diff --git a/lib/f2fs_metadata_crypt.c b/lib/f2fs_metadata_crypt.c
> new file mode 100644
> index 0000000..faf399a
> --- /dev/null
> +++ b/lib/f2fs_metadata_crypt.c
> @@ -0,0 +1,226 @@
> +/**
> + * f2fs_metadata_crypt.c
> + *
> + * Copyright (c) 2020 Google LLC
> + *
> + * Dual licensed under the GPL or LGPL version 2 licenses.
> + */
> +#include <string.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <unistd.h>
> +#include <sys/socket.h>
> +#include <linux/if_alg.h>
> +#include <linux/socket.h>
> +#include <assert.h>
> +#include <errno.h>
> +#include <keyutils.h>
> +
> +#include "f2fs_fs.h"
> +#include "f2fs_metadata_crypt.h"
> +
> +extern struct f2fs_configuration c;
> +struct f2fs_crypt_mode {
> +	const char *friendly_name;
> +	const char *cipher_str;
> +	unsigned int keysize;
> +	unsigned int ivlen;
> +} f2fs_crypt_modes[] = {
> +	[FSCRYPT_MODE_AES_256_XTS] = {
> +		.friendly_name = "AES-256-XTS",
> +		.cipher_str = "xts(aes)",
> +		.keysize = 64,
> +		.ivlen = 16,
> +	},
> +	[FSCRYPT_MODE_ADIANTUM] = {
> +		.friendly_name = "Adiantum",
> +		.cipher_str = "adiantum(xchacha12,aes)",
> +		.keysize = 32,
> +		.ivlen = 32,
> +	},
> +};
> +#define MAX_IV_LEN 32
> +
> +void f2fs_print_crypt_algs(void)
> +{
> +	int i;
> +
> +	for (i = 1; i <= __FSCRYPT_MODE_MAX; i++) {
> +		if (!f2fs_crypt_modes[i].friendly_name)
> +			continue;
> +		MSG(0, "\t%s\n", f2fs_crypt_modes[i].friendly_name);
> +	}
> +}
> +
> +int f2fs_get_crypt_alg(const char *optarg)
> +{
> +	int i;
> +
> +	for (i = 1; i <= __FSCRYPT_MODE_MAX; i++) {
> +		if (f2fs_crypt_modes[i].friendly_name &&
> +		    !strcmp(f2fs_crypt_modes[i].friendly_name, optarg)) {
> +			return i;
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +int f2fs_metadata_process_key(const char *key_serial_str)
> +{
> +	key_serial_t key_serial = strtol(key_serial_str, NULL, 10);
> +
> +	c.metadata_crypt_key_len =
> +		keyctl_read_alloc(key_serial, (void **)&c.metadata_crypt_key);
> +
> +	if (c.metadata_crypt_key_len < 0)
> +		return errno;
> +
> +	return 0;
> +}
> +
> +int f2fs_metadata_verify_args(void)
> +{
> +	/* If neither specified, nothing to do */
> +	if (!c.metadata_crypt_key && !c.metadata_crypt_alg)
> +		return 0;
> +
> +	/* We need both specified */
> +	if (!c.metadata_crypt_key || !c.metadata_crypt_alg)
> +		return -EINVAL;
> +
> +	if (c.metadata_crypt_key_len !=
> +	    f2fs_crypt_modes[c.metadata_crypt_alg].keysize) {
> +		MSG(0, "\tMetadata encryption key length %d didn't match required size %d\n",
> +		    c.metadata_crypt_key_len,
> +		    f2fs_crypt_modes[c.metadata_crypt_alg].keysize);
> +
> +		return -EINVAL;
> +	}

Need to check sparse mode here?

And, what about multiple partition case?

> +
> +	return 0;
> +}
> +
> +void f2fs_metadata_crypt_gen_iv(struct af_alg_iv *iv, __u64 blk_addr)
> +{
> +	int i = 0;
> +
> +	memset(iv->iv, 0, iv->ivlen);
> +
> +	while (blk_addr > 0) {
> +		iv->iv[i] = blk_addr & 0xFF;
> +		blk_addr >>= 8;
> +		i++;
> +	}
> +}
> +
> +int f2fs_metadata_crypt_block(void *buf, size_t len, __u64 blk_addr,
> +			      bool encrypt)
> +{
> +	struct f2fs_crypt_mode *crypt_mode;
> +	int sockfd, fd;
> +	struct sockaddr_alg sa = {
> +		.salg_family = AF_ALG,
> +		.salg_type = "skcipher",
> +	};
> +	struct msghdr msg = {};
> +	struct cmsghdr *cmsg;
> +	char cbuf[CMSG_SPACE(4) + CMSG_SPACE(4 + MAX_IV_LEN)] = {0};
> +	int blk_offset;
> +	struct af_alg_iv *iv;
> +	struct iovec iov;
> +	int err;
> +
> +	crypt_mode = &f2fs_crypt_modes[c.metadata_crypt_alg];
> +	memcpy(sa.salg_name, crypt_mode->cipher_str,
> +	       strlen(crypt_mode->cipher_str));
> +
> +	sockfd = socket(AF_ALG, SOCK_SEQPACKET, 0);
> +	if (sockfd < 0)
> +		return errno;
> +	err = bind(sockfd, (struct sockaddr *)&sa, sizeof(sa));
> +	if (err) {
> +		MSG(0, "\tCouldn't bind crypto socket. Maybe support for the crypto algorithm isn't enabled?\n");
> +		close(sockfd);
> +		return errno;
> +	}
> +	err = setsockopt(sockfd, SOL_ALG, ALG_SET_KEY, c.metadata_crypt_key,
> +			 crypt_mode->keysize);
> +	if (err) {
> +		MSG(0, "\tCouldn't set crypto socket options.\n");
> +		close(sockfd);
> +		return errno;
> +	}
> +	fd = accept(sockfd, NULL, 0);
> +	if (fd < 0)
> +		goto err_out;
> +
> +	msg.msg_control = cbuf;
> +	msg.msg_controllen = sizeof(cbuf);
> +
> +	cmsg = CMSG_FIRSTHDR(&msg);
> +	cmsg->cmsg_level = SOL_ALG;
> +	cmsg->cmsg_type = ALG_SET_OP;
> +	cmsg->cmsg_len = CMSG_LEN(4);
> +	*(__u32 *)CMSG_DATA(cmsg) = encrypt ? ALG_OP_ENCRYPT : ALG_OP_DECRYPT;
> +
> +	cmsg = CMSG_NXTHDR(&msg, cmsg);
> +	cmsg->cmsg_level = SOL_ALG;
> +	cmsg->cmsg_type = ALG_SET_IV;
> +	cmsg->cmsg_len = CMSG_LEN(4 + MAX_IV_LEN);
> +	iv = (void *)CMSG_DATA(cmsg);
> +	iv->ivlen = crypt_mode->ivlen;
> +
> +	iov.iov_len = F2FS_BLKSIZE;
> +
> +	msg.msg_iov = &iov;
> +	msg.msg_iovlen = 1;
> +
> +	for (blk_offset = 0; blk_offset < len / F2FS_BLKSIZE; blk_offset++) {
> +		f2fs_metadata_crypt_gen_iv(iv, blk_addr + blk_offset);
> +
> +		iov.iov_base = (char *)buf + blk_offset * F2FS_BLKSIZE;
> +
> +		err = sendmsg(fd, &msg, 0);
> +		if (err < 0)
> +			goto err_out;
> +		err = read(fd, (char *)buf + blk_offset * F2FS_BLKSIZE,
> +			   F2FS_BLKSIZE);
> +		if (err < 0)
> +			goto err_out;
> +	}
> +
> +	close(fd);
> +	close(sockfd);
> +
> +	return 0;
> +
> +err_out:
> +	err = errno;
> +	close(fd);
> +	close(sockfd);
> +
> +	return err;
> +}
> +
> +void *f2fs_metadata_crypt_blocks(void *buf, size_t len, __u64 blk_addr,
> +	bool encrypt)
> +{
> +	int err = 0;
> +	void *enc_buf;
> +
> +	if (!c.metadata_crypt_key)
> +		return buf;
> +
> +	enc_buf = malloc(len);
> +	memcpy(enc_buf, buf, len);
> +
> +	err = f2fs_metadata_crypt_block(enc_buf, len, blk_addr, encrypt);
> +	if (err) {
> +		MSG(0, "\tFailed to en/decrypt blocks. Errno %d\n", err);
> +		free(enc_buf);
> +		return NULL;
> +	}
> +
> +	return enc_buf;
> +}
> diff --git a/lib/libf2fs_io.c b/lib/libf2fs_io.c
> index 138285d..f117e1e 100644
> --- a/lib/libf2fs_io.c
> +++ b/lib/libf2fs_io.c
> @@ -33,6 +33,7 @@
>  #include <assert.h>
>  #include <inttypes.h>
>  #include "f2fs_fs.h"
> +#include "f2fs_metadata_crypt.h"
>  
>  struct f2fs_configuration c;
>  
> @@ -499,10 +500,12 @@ static int sparse_write_blk(__u64 block, int count, const void *buf) { return 0;
>  static int sparse_write_zeroed_blk(__u64 block, int count) { return 0; }
>  #endif
>  
> -int dev_read(void *buf, __u64 offset, size_t len)
> +int __dev_read(void *buf, __u64 offset, size_t len, bool unencrypted)
>  {
>  	int fd;
> +	__u64 blk_addr = offset >> F2FS_BLKSIZE_BITS;
>  	int err;
> +	void *new_buf = NULL;
>  
>  	if (c.sparse_mode)
>  		return sparse_read_blk(offset / F2FS_BLKSIZE,
> @@ -521,9 +524,29 @@ int dev_read(void *buf, __u64 offset, size_t len)
>  		return -1;
>  	if (read(fd, buf, len) < 0)
>  		return -1;
> +	if (!unencrypted) {
> +		new_buf = f2fs_metadata_crypt_blocks(buf, len, blk_addr, false);
> +		if (!new_buf)
> +			return -1;
> +		if (new_buf != buf) {
> +			memcpy(buf, new_buf, len);
> +			free(new_buf);
> +		}
> +	}
> +
>  	return 0;
>  }
>  
> +int dev_read(void *buf, __u64 offset, size_t len)
> +{
> +	return __dev_read(buf, offset, len, false);
> +}
> +
> +int dev_read_unencrypted(void *buf, __u64 offset, size_t len)
> +{
> +	return __dev_read(buf, offset, len, true);
> +}
> +
>  #ifdef POSIX_FADV_WILLNEED
>  int dev_readahead(__u64 offset, size_t len)
>  #else
> @@ -541,13 +564,17 @@ int dev_readahead(__u64 offset, size_t UNUSED(len))
>  #endif
>  }
>  
> -int dev_write(void *buf, __u64 offset, size_t len)
> +int __dev_write(void *buf, __u64 offset, size_t len, bool unencrypted)
>  {
>  	int fd;
> +	__u64 blk_addr = offset >> F2FS_BLKSIZE_BITS;
> +	void *src_buf = buf;
> +	int err = -1;
>  
>  	if (c.dry_run)
>  		return 0;
>  
> +	/* TODO: handle sparse mode with metadata encryption */
>  	if (c.sparse_mode)
>  		return sparse_write_blk(offset / F2FS_BLKSIZE,
>  					len / F2FS_BLKSIZE, buf);
> @@ -562,11 +589,26 @@ int dev_write(void *buf, __u64 offset, size_t len)
>  	 */
>  	if (dcache_update_cache(fd, buf, (off64_t)offset, len) < 0)
>  		return -1;
> +	if (!unencrypted) {
> +		buf = f2fs_metadata_crypt_blocks(buf, len, blk_addr, true);
> +		if (!buf)
> +			return -1;
> +	}
>  	if (lseek64(fd, (off64_t)offset, SEEK_SET) < 0)
> -		return -1;
> +		goto out;
>  	if (write(fd, buf, len) < 0)
> -		return -1;
> -	return 0;
> +		goto out;
> +
> +	err = 0;
> +out:
> +	if (buf != src_buf)
> +		free(buf);
> +	return err;
> +}
> +
> +int dev_write(void *buf, __u64 offset, size_t len)
> +{
> +	return __dev_write(buf, offset, len, false);
>  }
>  
>  int dev_write_block(void *buf, __u64 blk_addr)
> @@ -574,6 +616,16 @@ int dev_write_block(void *buf, __u64 blk_addr)
>  	return dev_write(buf, blk_addr << F2FS_BLKSIZE_BITS, F2FS_BLKSIZE);
>  }
>  
> +static int dev_write_unencrypted(void *buf, __u64 offset, size_t len)
> +{
> +	return __dev_write(buf, offset, len, true);
> +}
> +
> +int dev_write_block_unencrypted(void *buf, __u64 blk_addr)
> +{
> +	return dev_write_unencrypted(buf, blk_addr << F2FS_BLKSIZE_BITS, F2FS_BLKSIZE);
> +}
> +
>  int dev_write_dump(void *buf, __u64 offset, size_t len)
>  {
>  	if (lseek64(c.dump_fd, (off64_t)offset, SEEK_SET) < 0)
> @@ -586,7 +638,11 @@ int dev_write_dump(void *buf, __u64 offset, size_t len)
>  int dev_fill(void *buf, __u64 offset, size_t len)
>  {
>  	int fd;
> +	__u64 blk_addr = offset >> F2FS_BLKSIZE_BITS;
> +	void *src_buf = buf;
> +	int err = -1;
>  
> +	/* TODO: handle sparse mode with metadata encryption */
>  	if (c.sparse_mode)
>  		return sparse_write_zeroed_blk(offset / F2FS_BLKSIZE,
>  						len / F2FS_BLKSIZE);
> @@ -598,11 +654,21 @@ int dev_fill(void *buf, __u64 offset, size_t len)
>  	/* Only allow fill to zero */
>  	if (*((__u8*)buf))
>  		return -1;
> -	if (lseek64(fd, (off64_t)offset, SEEK_SET) < 0)
> +
> +	buf = f2fs_metadata_crypt_blocks(buf, len, blk_addr, true);
> +	if (!buf)
>  		return -1;
> +
> +	if (lseek64(fd, (off64_t)offset, SEEK_SET) < 0)
> +		goto out;
>  	if (write(fd, buf, len) < 0)
> -		return -1;
> -	return 0;
> +		goto out;
> +
> +	err = 0;
> +out:
> +	if (buf != src_buf)
> +		free(buf);
> +	return err;
>  }
>  
>  int dev_fill_block(void *buf, __u64 blk_addr)
> @@ -615,6 +681,11 @@ int dev_read_block(void *buf, __u64 blk_addr)
>  	return dev_read(buf, blk_addr << F2FS_BLKSIZE_BITS, F2FS_BLKSIZE);
>  }
>  
> +int dev_read_block_unencrypted(void *buf, __u64 blk_addr)
> +{
> +	return dev_read_unencrypted(buf, blk_addr << F2FS_BLKSIZE_BITS, F2FS_BLKSIZE);
> +}
> +
>  int dev_reada_block(__u64 blk_addr)
>  {
>  	return dev_readahead(blk_addr << F2FS_BLKSIZE_BITS, F2FS_BLKSIZE);
> diff --git a/mkfs/f2fs_format.c b/mkfs/f2fs_format.c
> index a6c542e..bf587bf 100644
> --- a/mkfs/f2fs_format.c
> +++ b/mkfs/f2fs_format.c
> @@ -537,6 +537,9 @@ static int f2fs_prepare_super_block(void)
>  		set_sb(s_encoding_flags, c.s_encoding_flags);
>  	}
>  
> +	if (c.metadata_crypt_key)
> +		set_sb(metadata_crypt_alg, c.metadata_crypt_alg);
> +
>  	sb->feature = c.feature;
>  
>  	if (get_sb(feature) & F2FS_FEATURE_SB_CHKSUM) {
> @@ -1046,7 +1049,7 @@ static int f2fs_write_super_block(void)
>  	memcpy(zero_buff + F2FS_SUPER_OFFSET, sb, sizeof(*sb));
>  	DBG(1, "\tWriting super block, at offset 0x%08x\n", 0);
>  	for (index = 0; index < 2; index++) {
> -		if (dev_write_block(zero_buff, index)) {
> +		if (dev_write_block_unencrypted(zero_buff, index)) {
>  			MSG(1, "\tError: While while writing super_blk "
>  					"on disk!!! index : %d\n", index);
>  			free(zero_buff);
> diff --git a/mkfs/f2fs_format_main.c b/mkfs/f2fs_format_main.c
> index f2f0a80..8856850 100644
> --- a/mkfs/f2fs_format_main.c
> +++ b/mkfs/f2fs_format_main.c
> @@ -28,6 +28,7 @@
>  
>  #include "f2fs_fs.h"
>  #include "f2fs_format_utils.h"
> +#include "f2fs_metadata_crypt.h"
>  
>  #ifdef WITH_ANDROID
>  #include <sparse/sparse.h>
> @@ -44,6 +45,7 @@ static void mkfs_usage()
>  	MSG(0, "\nUsage: mkfs.f2fs [options] device [sectors]\n");
>  	MSG(0, "[options]:\n");
>  	MSG(0, "  -a heap-based allocation [default:0]\n");
> +	MSG(0, "  -A Metadata encryption algorithm\n");
>  	MSG(0, "  -c device1[,device2,...] up to 7 additional devices, except meta device\n");
>  	MSG(0, "  -d debug level [default:0]\n");
>  	MSG(0, "  -e [cold file ext list] e.g. \"mp3,gif,mov\"\n");
> @@ -54,6 +56,7 @@ static void mkfs_usage()
>  	MSG(0, "  -l label\n");
>  	MSG(0, "  -U uuid\n");
>  	MSG(0, "  -m support zoned block device [default:0]\n");
> +	MSG(0, "  -M Metadata encryption key_serial in keyring\n");
>  	MSG(0, "  -o overprovision percentage [default:auto]\n");
>  	MSG(0, "  -O feature1[,feature2,...] e.g. \"encrypt\"\n");
>  	MSG(0, "  -C [encoding[:flag1,...]] Support casefolding with optional flags\n");
> @@ -97,6 +100,12 @@ static void f2fs_show_info()
>  					f2fs_encoding2str(c.s_encoding));
>  	if (c.feature & le32_to_cpu(F2FS_FEATURE_PRJQUOTA))
>  		MSG(0, "Info: Enable Project quota\n");
> +
> +	if (c.metadata_crypt_key)
> +		MSG(0, "Info: Metadata key is %s\n", c.metadata_crypt_key);
> +
> +	if (c.metadata_crypt_alg)
> +		MSG(0, "Info: Metadata alg is %d\n", c.metadata_crypt_alg);
>  }
>  
>  static void add_default_options(void)
> @@ -125,7 +134,7 @@ static void add_default_options(void)
>  
>  static void f2fs_parse_options(int argc, char *argv[])
>  {
> -	static const char *option_string = "qa:c:C:d:e:E:g:il:mo:O:rR:s:S:z:t:T:U:Vfw:";
> +	static const char *option_string = "qa:A:c:C:d:e:E:g:il:mM:o:O:rR:s:S:z:t:T:U:Vfw:";
>  	int32_t option=0;
>  	int val;
>  	char *token;
> @@ -138,6 +147,14 @@ static void f2fs_parse_options(int argc, char *argv[])
>  		case 'a':
>  			c.heap = atoi(optarg);
>  			break;
> +		case 'A':
> +			c.metadata_crypt_alg = f2fs_get_crypt_alg(optarg);
> +			if (c.metadata_crypt_alg < 0) {
> +				MSG(0, "Error: invalid crypt algorithm specified. The choices are:");
> +				f2fs_print_crypt_algs();
> +				exit(1);
> +			}
> +			break;
>  		case 'c':
>  			if (c.ndevs >= MAX_DEVICES) {
>  				MSG(0, "Error: Too many devices\n");
> @@ -178,6 +195,12 @@ static void f2fs_parse_options(int argc, char *argv[])
>  		case 'm':
>  			c.zoned_mode = 1;
>  			break;
> +		case 'M':
> +			if (f2fs_metadata_process_key(optarg)) {
> +				MSG(0, "Error: Invalid metadata key\n");
> +				mkfs_usage();
> +			}
> +			break;
>  		case 'o':
>  			c.overprovision = atof(optarg);
>  			break;
> @@ -244,6 +267,14 @@ static void f2fs_parse_options(int argc, char *argv[])
>  		}
>  	}
>  
> +	if ((!!c.metadata_crypt_key) != (!!c.metadata_crypt_alg)) {
> +		MSG(0, "\tError: Both the metadata crypt key and crypt algorithm must be specified!");
> +		exit(1);
> +	}
> +
> +	if (f2fs_metadata_verify_args())
> +		exit(1);
> +
>  	add_default_options();

Need to check options after add_default_options()?

Thanks,

>  
>  	if (!(c.feature & cpu_to_le32(F2FS_FEATURE_EXTRA_ATTR))) {
> -- 
> 2.28.0.806.g8561365e88-goog
Eric Biggers Oct. 7, 2020, 9:52 p.m. UTC | #2
On Mon, Oct 05, 2020 at 07:41:33AM +0000, Satya Tangirala wrote:
> diff --git a/lib/Makefile.am b/lib/Makefile.am
> index 871d773..a82d753 100644
> --- a/lib/Makefile.am
> +++ b/lib/Makefile.am
> @@ -2,10 +2,10 @@
>  
>  lib_LTLIBRARIES = libf2fs.la
>  
> -libf2fs_la_SOURCES = libf2fs.c libf2fs_io.c libf2fs_zoned.c nls_utf8.c
> +libf2fs_la_SOURCES = libf2fs.c libf2fs_io.c libf2fs_zoned.c nls_utf8.c f2fs_metadata_crypt.c
>  libf2fs_la_CFLAGS = -Wall
>  libf2fs_la_CPPFLAGS = -I$(top_srcdir)/include
> -libf2fs_la_LDFLAGS = -version-info $(LIBF2FS_CURRENT):$(LIBF2FS_REVISION):$(LIBF2FS_AGE)
> +libf2fs_la_LDFLAGS = -lkeyutils -version-info $(LIBF2FS_CURRENT):$(LIBF2FS_REVISION):$(LIBF2FS_AGE)

This introduces a dependency on libkeyutils.  Doesn't that need to be checked in
configure.ac?

> diff --git a/lib/f2fs_metadata_crypt.c b/lib/f2fs_metadata_crypt.c
> new file mode 100644
> index 0000000..faf399a
> --- /dev/null
> +++ b/lib/f2fs_metadata_crypt.c
> @@ -0,0 +1,226 @@
> +/**
> + * f2fs_metadata_crypt.c
> + *
> + * Copyright (c) 2020 Google LLC
> + *
> + * Dual licensed under the GPL or LGPL version 2 licenses.
> + */
> +#include <string.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <unistd.h>
> +#include <sys/socket.h>
> +#include <linux/if_alg.h>
> +#include <linux/socket.h>
> +#include <assert.h>
> +#include <errno.h>
> +#include <keyutils.h>
> +
> +#include "f2fs_fs.h"
> +#include "f2fs_metadata_crypt.h"
> +
> +extern struct f2fs_configuration c;
> +struct f2fs_crypt_mode {
> +	const char *friendly_name;
> +	const char *cipher_str;
> +	unsigned int keysize;
> +	unsigned int ivlen;
> +} f2fs_crypt_modes[] = {

Use 'const' for static or global data that isn't modified.

> +void f2fs_print_crypt_algs(void)
> +{
> +	int i;
> +
> +	for (i = 1; i <= __FSCRYPT_MODE_MAX; i++) {
> +		if (!f2fs_crypt_modes[i].friendly_name)
> +			continue;
> +		MSG(0, "\t%s\n", f2fs_crypt_modes[i].friendly_name);
> +	}
> +}
> +
> +int f2fs_get_crypt_alg(const char *optarg)
> +{
> +	int i;
> +
> +	for (i = 1; i <= __FSCRYPT_MODE_MAX; i++) {
> +		if (f2fs_crypt_modes[i].friendly_name &&
> +		    !strcmp(f2fs_crypt_modes[i].friendly_name, optarg)) {
> +			return i;
> +		}
> +	}
> +
> +	return 0;
> +}

Although __FSCRYPT_MODE_MAX is defined in <linux/fscrypt.h>, it isn't intended
to be used in userspace programs, as its value will change depending on the
version of the kernel headers.  Just use ARRAY_SIZE(f2fs_crypt_modes) instead.

> +int f2fs_metadata_crypt_block(void *buf, size_t len, __u64 blk_addr,
> +			      bool encrypt)
> +{
> +	struct f2fs_crypt_mode *crypt_mode;
> +	int sockfd, fd;
> +	struct sockaddr_alg sa = {
> +		.salg_family = AF_ALG,
> +		.salg_type = "skcipher",
> +	};
> +	struct msghdr msg = {};
> +	struct cmsghdr *cmsg;
> +	char cbuf[CMSG_SPACE(4) + CMSG_SPACE(4 + MAX_IV_LEN)] = {0};
> +	int blk_offset;
> +	struct af_alg_iv *iv;
> +	struct iovec iov;
> +	int err;
> +
> +	crypt_mode = &f2fs_crypt_modes[c.metadata_crypt_alg];
> +	memcpy(sa.salg_name, crypt_mode->cipher_str,
> +	       strlen(crypt_mode->cipher_str));
> +
> +	sockfd = socket(AF_ALG, SOCK_SEQPACKET, 0);
> +	if (sockfd < 0)
> +		return errno;

This will fail if AF_ALG isn't enabled in the kernel config, or if the process
isn't allowed to use AF_ALG by SELinux policy.  Can you show a proper error
message?

> +	err = bind(sockfd, (struct sockaddr *)&sa, sizeof(sa));
> +	if (err) {
> +		MSG(0, "\tCouldn't bind crypto socket. Maybe support for the crypto algorithm isn't enabled?\n");
> +		close(sockfd);
> +		return errno;
> +	}

This will fail if either CRYPTO_USER_API_SKCIPHER isn't enabled in the kernel
config, or if the required crypto algorithm isn't enabled in the kernel config.
Can you show a better error message?

Also, these new kernel config option dependencies should be documented in the
documentation for f2fs-tools.

> +	err = setsockopt(sockfd, SOL_ALG, ALG_SET_KEY, c.metadata_crypt_key,
> +			 crypt_mode->keysize);
> +	if (err) {
> +		MSG(0, "\tCouldn't set crypto socket options.\n");
> +		close(sockfd);
> +		return errno;
> +	}
> +	fd = accept(sockfd, NULL, 0);
> +	if (fd < 0)
> +		goto err_out;

It's a lot of work to allocate an AF_ALG algorithm socket, set the key, and
allocate a request socket for every block.  Can any of this be cached?  For
single threaded use, it seems the request socket can be cached; otherwise the
algorithm socket with a key set can be cached.

- Eric
diff mbox series

Patch

diff --git a/fsck/main.c b/fsck/main.c
index 32559f1..6a4d867 100644
--- a/fsck/main.c
+++ b/fsck/main.c
@@ -26,6 +26,8 @@ 
 #include <stdbool.h>
 #include "quotaio.h"
 
+#include "f2fs_metadata_crypt.h"
+
 struct f2fs_fsck gfsck;
 
 #ifdef WITH_ANDROID
@@ -62,6 +64,7 @@  void fsck_usage()
 			" (default 0)\n");
 	MSG(0, "  -m <max-hash-collision>  set max cache hash collision"
 			" (default 16)\n");
+	MSG(0, "  -M Metadata encryption key_serial in keyring\n");
 	MSG(0, "  -C encoding[:flag1,flag2] Set options for enabling"
 			" casefolding\n");
 	MSG(0, "  -d debug level [default:0]\n");
@@ -92,6 +95,7 @@  void dump_usage()
 	MSG(0, "  -S sparse_mode\n");
 	MSG(0, "  -a [SSA dump segno from #1~#2 (decimal), for all 0~-1]\n");
 	MSG(0, "  -b blk_addr (in 4KB)\n");
+	MSG(0, "  -M Metadata encryption key_serial in keyring\n");
 	MSG(0, "  -V print the version number and exit\n");
 
 	exit(1);
@@ -107,6 +111,7 @@  void defrag_usage()
 	MSG(0, "  -l length [default:512 (2MB)]\n");
 	MSG(0, "  -t target block address [default: main_blkaddr + 2MB]\n");
 	MSG(0, "  -i set direction as shrink [default: expand]\n");
+	MSG(0, "  -M Metadata encryption key_serial in keyring\n");
 	MSG(0, "  -V print the version number and exit\n");
 	exit(1);
 }
@@ -119,6 +124,7 @@  void resize_usage()
 	MSG(0, "  -i extended node bitmap, node ratio is 20%% by default\n");
 	MSG(0, "  -s safe resize (Does not resize metadata)");
 	MSG(0, "  -t target sectors [default: device size]\n");
+	MSG(0, "  -M Metadata encryption key_serial in keyring\n");
 	MSG(0, "  -V print the version number and exit\n");
 	exit(1);
 }
@@ -129,6 +135,7 @@  void sload_usage()
 	MSG(0, "[options]:\n");
 	MSG(0, "  -C fs_config\n");
 	MSG(0, "  -f source directory [path of the source directory]\n");
+	MSG(0, "  -M Metadata encryption key_serial in keyring\n");
 	MSG(0, "  -p product out directory\n");
 	MSG(0, "  -s file_contexts\n");
 	MSG(0, "  -S sparse_mode\n");
@@ -200,7 +207,7 @@  void f2fs_parse_options(int argc, char *argv[])
 	}
 
 	if (!strcmp("fsck.f2fs", prog)) {
-		const char *option_string = ":aC:c:m:d:fg:O:p:q:StyV";
+		const char *option_string = ":aC:c:m:M:d:fg:O:p:q:StyV";
 		int opt = 0, val;
 		char *token;
 		struct option long_opt[] = {
@@ -243,6 +250,12 @@  void f2fs_parse_options(int argc, char *argv[])
 				c.cache_config.max_hash_collision =
 						atoi(optarg);
 				break;
+			case 'M':
+				if (f2fs_metadata_process_key(optarg)) {
+					MSG(0, "Error: Invalid metadata key\n");
+					fsck_usage();
+				}
+				break;
 			case 'g':
 				if (!strcmp(optarg, "android"))
 					c.defset = CONF_ANDROID;
@@ -345,7 +358,7 @@  void f2fs_parse_options(int argc, char *argv[])
 				break;
 		}
 	} else if (!strcmp("dump.f2fs", prog)) {
-		const char *option_string = "d:i:n:s:Sa:b:V";
+		const char *option_string = "d:i:n:s:Sa:b:M:V";
 		static struct dump_option dump_opt = {
 			.nid = 0,	/* default root ino */
 			.start_nat = -1,
@@ -413,6 +426,12 @@  void f2fs_parse_options(int argc, char *argv[])
 					ret = sscanf(optarg, "%x",
 							&dump_opt.blk_addr);
 				break;
+			case 'M':
+				if (f2fs_metadata_process_key(optarg)) {
+					MSG(0, "Error: Invalid metadata key\n");
+					dump_usage();
+				}
+				break;
 			case 'V':
 				show_version(prog);
 				exit(0);
@@ -427,7 +446,7 @@  void f2fs_parse_options(int argc, char *argv[])
 
 		c.private = &dump_opt;
 	} else if (!strcmp("defrag.f2fs", prog)) {
-		const char *option_string = "d:s:Sl:t:iV";
+		const char *option_string = "d:M:s:Sl:t:iV";
 
 		c.func = DEFRAG;
 		while ((option = getopt(argc, argv, option_string)) != EOF) {
@@ -473,6 +492,12 @@  void f2fs_parse_options(int argc, char *argv[])
 			case 'i':
 				c.defrag_shrink = 1;
 				break;
+			case 'M':
+				if (f2fs_metadata_process_key(optarg)) {
+					MSG(0, "Error: Invalid metadata key\n");
+					defrag_usage();
+				}
+				break;
 			case 'V':
 				show_version(prog);
 				exit(0);
@@ -485,7 +510,7 @@  void f2fs_parse_options(int argc, char *argv[])
 				break;
 		}
 	} else if (!strcmp("resize.f2fs", prog)) {
-		const char *option_string = "d:st:iV";
+		const char *option_string = "d:M:st:iV";
 
 		c.func = RESIZE;
 		while ((option = getopt(argc, argv, option_string)) != EOF) {
@@ -515,6 +540,12 @@  void f2fs_parse_options(int argc, char *argv[])
 			case 'i':
 				c.large_nat_bitmap = 1;
 				break;
+			case 'M':
+				if (f2fs_metadata_process_key(optarg)) {
+					MSG(0, "Error: Invalid metadata key\n");
+					resize_usage();
+				}
+				break;
 			case 'V':
 				show_version(prog);
 				exit(0);
@@ -527,7 +558,7 @@  void f2fs_parse_options(int argc, char *argv[])
 				break;
 		}
 	} else if (!strcmp("sload.f2fs", prog)) {
-		const char *option_string = "C:d:f:p:s:St:T:V";
+		const char *option_string = "C:d:f:M:p:s:St:T:V";
 #ifdef HAVE_LIBSELINUX
 		int max_nr_opt = (int)sizeof(c.seopt_file) /
 			sizeof(c.seopt_file[0]);
@@ -553,6 +584,12 @@  void f2fs_parse_options(int argc, char *argv[])
 			case 'f':
 				c.from_dir = absolute_path(optarg);
 				break;
+			case 'M':
+				if (f2fs_metadata_process_key(optarg)) {
+					MSG(0, "Error: Invalid metadata key\n");
+					sload_usage();
+				}
+				break;
 			case 'p':
 				c.target_out_dir = absolute_path(optarg);
 				break;
diff --git a/fsck/mount.c b/fsck/mount.c
index 8ebc5b0..7520a8a 100644
--- a/fsck/mount.c
+++ b/fsck/mount.c
@@ -11,6 +11,7 @@ 
 #include "fsck.h"
 #include "node.h"
 #include "xattr.h"
+#include "f2fs_metadata_crypt.h"
 #include <locale.h>
 #include <stdbool.h>
 #ifdef HAVE_LINUX_POSIX_ACL_H
@@ -561,6 +562,10 @@  void print_sb_state(struct f2fs_super_block *sb)
 	if (f & cpu_to_le32(F2FS_FEATURE_COMPRESSION)) {
 		MSG(0, "%s", " compression");
 	}
+	if (sb->metadata_crypt_alg) {
+		MSG(0, "%s", " metadata_crypt");
+	}
+
 	MSG(0, "\n");
 	MSG(0, "Info: superblock encrypt level = %d, salt = ",
 					sb->encryption_level);
@@ -686,7 +691,7 @@  void update_superblock(struct f2fs_super_block *sb, int sb_mask)
 	memcpy(buf + F2FS_SUPER_OFFSET, sb, sizeof(*sb));
 	for (addr = SB0_ADDR; addr < SB_MAX_ADDR; addr++) {
 		if (SB_MASK(addr) & sb_mask) {
-			ret = dev_write_block(buf, addr);
+			ret = dev_write_block_unencrypted(buf, addr);
 			ASSERT(ret >= 0);
 		}
 	}
@@ -927,6 +932,24 @@  int sanity_check_raw_super(struct f2fs_super_block *sb, enum SB_ADDR sb_addr)
 		return -1;
 	}
 
+	/*
+	 * Check that metadata encryption is enabled on superblock when metadata
+	 * crypt key is specified
+	 */
+	if (get_sb(metadata_crypt_alg) && !c.metadata_crypt_key) {
+		MSG(0, "\tFilesystem has metadata encryption, but we're missing the metadata encryption key.\n");
+		return -1;
+	}
+
+	/*
+	 * Check that metadata encryption is disabled on superblock when metadata
+	 * crypt key is not specified
+	 */
+	if (!get_sb(metadata_crypt_alg) && c.metadata_crypt_key) {
+		MSG(0, "\tFilesystem has does not have metadata encryption, but a metadata encryption key was specified.\n");
+		return -1;
+	}
+
 	if (sanity_check_area_boundary(sb, sb_addr))
 		return -1;
 	return 0;
@@ -940,7 +963,7 @@  int validate_super_block(struct f2fs_sb_info *sbi, enum SB_ADDR sb_addr)
 	if (!sbi->raw_super)
 		return -ENOMEM;
 
-	if (dev_read_block(buf, sb_addr))
+	if (dev_read_block_unencrypted(buf, sb_addr))
 		return -1;
 
 	memcpy(sbi->raw_super, buf + F2FS_SUPER_OFFSET,
@@ -3499,6 +3522,12 @@  int f2fs_do_mount(struct f2fs_sb_info *sbi)
 	}
 	sb = F2FS_RAW_SUPER(sbi);
 
+	/* Get metadata encryption algorithm */
+	c.metadata_crypt_alg = get_sb(metadata_crypt_alg);
+
+	if (f2fs_metadata_verify_args())
+		exit(1);
+
 	ret = check_sector_size(sb);
 	if (ret)
 		return -1;
diff --git a/include/f2fs_fs.h b/include/f2fs_fs.h
index b5bda13..6b1912d 100644
--- a/include/f2fs_fs.h
+++ b/include/f2fs_fs.h
@@ -441,6 +441,11 @@  struct f2fs_configuration {
 
 	/* cache parameters */
 	dev_cache_config_t cache_config;
+
+	/* metadata encryption */
+	__u8 *metadata_crypt_key;
+	int metadata_crypt_key_len;
+	int metadata_crypt_alg;
 };
 
 #ifdef CONFIG_64BIT
@@ -675,7 +680,8 @@  struct f2fs_super_block {
 	__u8 hot_ext_count;		/* # of hot file extension */
 	__le16  s_encoding;		/* Filename charset encoding */
 	__le16  s_encoding_flags;	/* Filename charset encoding flags */
-	__u8 reserved[306];		/* valid reserved region */
+	__le32	metadata_crypt_alg;	/* The metadata encryption algorithm (FSCRYPT_MODE_*) */
+	__u8 reserved[302];		/* valid reserved region */
 	__le32 crc;			/* checksum of superblock */
 } __attribute__((packed));
 
@@ -1237,12 +1243,14 @@  extern int dev_readahead(__u64, size_t UNUSED(len));
 #endif
 extern int dev_write(void *, __u64, size_t);
 extern int dev_write_block(void *, __u64);
+extern int dev_write_block_unencrypted(void *, __u64);
 extern int dev_write_dump(void *, __u64, size_t);
 /* All bytes in the buffer must be 0 use dev_fill(). */
 extern int dev_fill(void *, __u64, size_t);
 extern int dev_fill_block(void *, __u64);
 
 extern int dev_read_block(void *, __u64);
+extern int dev_read_block_unencrypted(void *, __u64);
 extern int dev_reada_block(__u64);
 
 extern int dev_read_version(void *, __u64, size_t);
diff --git a/include/f2fs_metadata_crypt.h b/include/f2fs_metadata_crypt.h
new file mode 100644
index 0000000..d15873d
--- /dev/null
+++ b/include/f2fs_metadata_crypt.h
@@ -0,0 +1,21 @@ 
+/**
+ * f2fs_metadata_crypt.h
+ *
+ * Copyright (c) 2020 Google LLC
+ *
+ * Dual licensed under the GPL or LGPL version 2 licenses.
+ */
+
+#include <inttypes.h>
+#include <linux/fscrypt.h>
+
+int f2fs_get_crypt_alg(const char *optarg);
+
+void f2fs_print_crypt_algs(void);
+
+int f2fs_metadata_process_key(const char *key_serial);
+
+int f2fs_metadata_verify_args(void);
+
+void *f2fs_metadata_crypt_blocks(void *src_buf, size_t len, __u64 blk_addr,
+	bool encrypt);
diff --git a/lib/Makefile.am b/lib/Makefile.am
index 871d773..a82d753 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -2,10 +2,10 @@ 
 
 lib_LTLIBRARIES = libf2fs.la
 
-libf2fs_la_SOURCES = libf2fs.c libf2fs_io.c libf2fs_zoned.c nls_utf8.c
+libf2fs_la_SOURCES = libf2fs.c libf2fs_io.c libf2fs_zoned.c nls_utf8.c f2fs_metadata_crypt.c
 libf2fs_la_CFLAGS = -Wall
 libf2fs_la_CPPFLAGS = -I$(top_srcdir)/include
-libf2fs_la_LDFLAGS = -version-info $(LIBF2FS_CURRENT):$(LIBF2FS_REVISION):$(LIBF2FS_AGE)
+libf2fs_la_LDFLAGS = -lkeyutils -version-info $(LIBF2FS_CURRENT):$(LIBF2FS_REVISION):$(LIBF2FS_AGE)
 
 root_libdir=@root_libdir@
 
diff --git a/lib/f2fs_metadata_crypt.c b/lib/f2fs_metadata_crypt.c
new file mode 100644
index 0000000..faf399a
--- /dev/null
+++ b/lib/f2fs_metadata_crypt.c
@@ -0,0 +1,226 @@ 
+/**
+ * f2fs_metadata_crypt.c
+ *
+ * Copyright (c) 2020 Google LLC
+ *
+ * Dual licensed under the GPL or LGPL version 2 licenses.
+ */
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <linux/if_alg.h>
+#include <linux/socket.h>
+#include <assert.h>
+#include <errno.h>
+#include <keyutils.h>
+
+#include "f2fs_fs.h"
+#include "f2fs_metadata_crypt.h"
+
+extern struct f2fs_configuration c;
+struct f2fs_crypt_mode {
+	const char *friendly_name;
+	const char *cipher_str;
+	unsigned int keysize;
+	unsigned int ivlen;
+} f2fs_crypt_modes[] = {
+	[FSCRYPT_MODE_AES_256_XTS] = {
+		.friendly_name = "AES-256-XTS",
+		.cipher_str = "xts(aes)",
+		.keysize = 64,
+		.ivlen = 16,
+	},
+	[FSCRYPT_MODE_ADIANTUM] = {
+		.friendly_name = "Adiantum",
+		.cipher_str = "adiantum(xchacha12,aes)",
+		.keysize = 32,
+		.ivlen = 32,
+	},
+};
+#define MAX_IV_LEN 32
+
+void f2fs_print_crypt_algs(void)
+{
+	int i;
+
+	for (i = 1; i <= __FSCRYPT_MODE_MAX; i++) {
+		if (!f2fs_crypt_modes[i].friendly_name)
+			continue;
+		MSG(0, "\t%s\n", f2fs_crypt_modes[i].friendly_name);
+	}
+}
+
+int f2fs_get_crypt_alg(const char *optarg)
+{
+	int i;
+
+	for (i = 1; i <= __FSCRYPT_MODE_MAX; i++) {
+		if (f2fs_crypt_modes[i].friendly_name &&
+		    !strcmp(f2fs_crypt_modes[i].friendly_name, optarg)) {
+			return i;
+		}
+	}
+
+	return 0;
+}
+
+int f2fs_metadata_process_key(const char *key_serial_str)
+{
+	key_serial_t key_serial = strtol(key_serial_str, NULL, 10);
+
+	c.metadata_crypt_key_len =
+		keyctl_read_alloc(key_serial, (void **)&c.metadata_crypt_key);
+
+	if (c.metadata_crypt_key_len < 0)
+		return errno;
+
+	return 0;
+}
+
+int f2fs_metadata_verify_args(void)
+{
+	/* If neither specified, nothing to do */
+	if (!c.metadata_crypt_key && !c.metadata_crypt_alg)
+		return 0;
+
+	/* We need both specified */
+	if (!c.metadata_crypt_key || !c.metadata_crypt_alg)
+		return -EINVAL;
+
+	if (c.metadata_crypt_key_len !=
+	    f2fs_crypt_modes[c.metadata_crypt_alg].keysize) {
+		MSG(0, "\tMetadata encryption key length %d didn't match required size %d\n",
+		    c.metadata_crypt_key_len,
+		    f2fs_crypt_modes[c.metadata_crypt_alg].keysize);
+
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+void f2fs_metadata_crypt_gen_iv(struct af_alg_iv *iv, __u64 blk_addr)
+{
+	int i = 0;
+
+	memset(iv->iv, 0, iv->ivlen);
+
+	while (blk_addr > 0) {
+		iv->iv[i] = blk_addr & 0xFF;
+		blk_addr >>= 8;
+		i++;
+	}
+}
+
+int f2fs_metadata_crypt_block(void *buf, size_t len, __u64 blk_addr,
+			      bool encrypt)
+{
+	struct f2fs_crypt_mode *crypt_mode;
+	int sockfd, fd;
+	struct sockaddr_alg sa = {
+		.salg_family = AF_ALG,
+		.salg_type = "skcipher",
+	};
+	struct msghdr msg = {};
+	struct cmsghdr *cmsg;
+	char cbuf[CMSG_SPACE(4) + CMSG_SPACE(4 + MAX_IV_LEN)] = {0};
+	int blk_offset;
+	struct af_alg_iv *iv;
+	struct iovec iov;
+	int err;
+
+	crypt_mode = &f2fs_crypt_modes[c.metadata_crypt_alg];
+	memcpy(sa.salg_name, crypt_mode->cipher_str,
+	       strlen(crypt_mode->cipher_str));
+
+	sockfd = socket(AF_ALG, SOCK_SEQPACKET, 0);
+	if (sockfd < 0)
+		return errno;
+	err = bind(sockfd, (struct sockaddr *)&sa, sizeof(sa));
+	if (err) {
+		MSG(0, "\tCouldn't bind crypto socket. Maybe support for the crypto algorithm isn't enabled?\n");
+		close(sockfd);
+		return errno;
+	}
+	err = setsockopt(sockfd, SOL_ALG, ALG_SET_KEY, c.metadata_crypt_key,
+			 crypt_mode->keysize);
+	if (err) {
+		MSG(0, "\tCouldn't set crypto socket options.\n");
+		close(sockfd);
+		return errno;
+	}
+	fd = accept(sockfd, NULL, 0);
+	if (fd < 0)
+		goto err_out;
+
+	msg.msg_control = cbuf;
+	msg.msg_controllen = sizeof(cbuf);
+
+	cmsg = CMSG_FIRSTHDR(&msg);
+	cmsg->cmsg_level = SOL_ALG;
+	cmsg->cmsg_type = ALG_SET_OP;
+	cmsg->cmsg_len = CMSG_LEN(4);
+	*(__u32 *)CMSG_DATA(cmsg) = encrypt ? ALG_OP_ENCRYPT : ALG_OP_DECRYPT;
+
+	cmsg = CMSG_NXTHDR(&msg, cmsg);
+	cmsg->cmsg_level = SOL_ALG;
+	cmsg->cmsg_type = ALG_SET_IV;
+	cmsg->cmsg_len = CMSG_LEN(4 + MAX_IV_LEN);
+	iv = (void *)CMSG_DATA(cmsg);
+	iv->ivlen = crypt_mode->ivlen;
+
+	iov.iov_len = F2FS_BLKSIZE;
+
+	msg.msg_iov = &iov;
+	msg.msg_iovlen = 1;
+
+	for (blk_offset = 0; blk_offset < len / F2FS_BLKSIZE; blk_offset++) {
+		f2fs_metadata_crypt_gen_iv(iv, blk_addr + blk_offset);
+
+		iov.iov_base = (char *)buf + blk_offset * F2FS_BLKSIZE;
+
+		err = sendmsg(fd, &msg, 0);
+		if (err < 0)
+			goto err_out;
+		err = read(fd, (char *)buf + blk_offset * F2FS_BLKSIZE,
+			   F2FS_BLKSIZE);
+		if (err < 0)
+			goto err_out;
+	}
+
+	close(fd);
+	close(sockfd);
+
+	return 0;
+
+err_out:
+	err = errno;
+	close(fd);
+	close(sockfd);
+
+	return err;
+}
+
+void *f2fs_metadata_crypt_blocks(void *buf, size_t len, __u64 blk_addr,
+	bool encrypt)
+{
+	int err = 0;
+	void *enc_buf;
+
+	if (!c.metadata_crypt_key)
+		return buf;
+
+	enc_buf = malloc(len);
+	memcpy(enc_buf, buf, len);
+
+	err = f2fs_metadata_crypt_block(enc_buf, len, blk_addr, encrypt);
+	if (err) {
+		MSG(0, "\tFailed to en/decrypt blocks. Errno %d\n", err);
+		free(enc_buf);
+		return NULL;
+	}
+
+	return enc_buf;
+}
diff --git a/lib/libf2fs_io.c b/lib/libf2fs_io.c
index 138285d..f117e1e 100644
--- a/lib/libf2fs_io.c
+++ b/lib/libf2fs_io.c
@@ -33,6 +33,7 @@ 
 #include <assert.h>
 #include <inttypes.h>
 #include "f2fs_fs.h"
+#include "f2fs_metadata_crypt.h"
 
 struct f2fs_configuration c;
 
@@ -499,10 +500,12 @@  static int sparse_write_blk(__u64 block, int count, const void *buf) { return 0;
 static int sparse_write_zeroed_blk(__u64 block, int count) { return 0; }
 #endif
 
-int dev_read(void *buf, __u64 offset, size_t len)
+int __dev_read(void *buf, __u64 offset, size_t len, bool unencrypted)
 {
 	int fd;
+	__u64 blk_addr = offset >> F2FS_BLKSIZE_BITS;
 	int err;
+	void *new_buf = NULL;
 
 	if (c.sparse_mode)
 		return sparse_read_blk(offset / F2FS_BLKSIZE,
@@ -521,9 +524,29 @@  int dev_read(void *buf, __u64 offset, size_t len)
 		return -1;
 	if (read(fd, buf, len) < 0)
 		return -1;
+	if (!unencrypted) {
+		new_buf = f2fs_metadata_crypt_blocks(buf, len, blk_addr, false);
+		if (!new_buf)
+			return -1;
+		if (new_buf != buf) {
+			memcpy(buf, new_buf, len);
+			free(new_buf);
+		}
+	}
+
 	return 0;
 }
 
+int dev_read(void *buf, __u64 offset, size_t len)
+{
+	return __dev_read(buf, offset, len, false);
+}
+
+int dev_read_unencrypted(void *buf, __u64 offset, size_t len)
+{
+	return __dev_read(buf, offset, len, true);
+}
+
 #ifdef POSIX_FADV_WILLNEED
 int dev_readahead(__u64 offset, size_t len)
 #else
@@ -541,13 +564,17 @@  int dev_readahead(__u64 offset, size_t UNUSED(len))
 #endif
 }
 
-int dev_write(void *buf, __u64 offset, size_t len)
+int __dev_write(void *buf, __u64 offset, size_t len, bool unencrypted)
 {
 	int fd;
+	__u64 blk_addr = offset >> F2FS_BLKSIZE_BITS;
+	void *src_buf = buf;
+	int err = -1;
 
 	if (c.dry_run)
 		return 0;
 
+	/* TODO: handle sparse mode with metadata encryption */
 	if (c.sparse_mode)
 		return sparse_write_blk(offset / F2FS_BLKSIZE,
 					len / F2FS_BLKSIZE, buf);
@@ -562,11 +589,26 @@  int dev_write(void *buf, __u64 offset, size_t len)
 	 */
 	if (dcache_update_cache(fd, buf, (off64_t)offset, len) < 0)
 		return -1;
+	if (!unencrypted) {
+		buf = f2fs_metadata_crypt_blocks(buf, len, blk_addr, true);
+		if (!buf)
+			return -1;
+	}
 	if (lseek64(fd, (off64_t)offset, SEEK_SET) < 0)
-		return -1;
+		goto out;
 	if (write(fd, buf, len) < 0)
-		return -1;
-	return 0;
+		goto out;
+
+	err = 0;
+out:
+	if (buf != src_buf)
+		free(buf);
+	return err;
+}
+
+int dev_write(void *buf, __u64 offset, size_t len)
+{
+	return __dev_write(buf, offset, len, false);
 }
 
 int dev_write_block(void *buf, __u64 blk_addr)
@@ -574,6 +616,16 @@  int dev_write_block(void *buf, __u64 blk_addr)
 	return dev_write(buf, blk_addr << F2FS_BLKSIZE_BITS, F2FS_BLKSIZE);
 }
 
+static int dev_write_unencrypted(void *buf, __u64 offset, size_t len)
+{
+	return __dev_write(buf, offset, len, true);
+}
+
+int dev_write_block_unencrypted(void *buf, __u64 blk_addr)
+{
+	return dev_write_unencrypted(buf, blk_addr << F2FS_BLKSIZE_BITS, F2FS_BLKSIZE);
+}
+
 int dev_write_dump(void *buf, __u64 offset, size_t len)
 {
 	if (lseek64(c.dump_fd, (off64_t)offset, SEEK_SET) < 0)
@@ -586,7 +638,11 @@  int dev_write_dump(void *buf, __u64 offset, size_t len)
 int dev_fill(void *buf, __u64 offset, size_t len)
 {
 	int fd;
+	__u64 blk_addr = offset >> F2FS_BLKSIZE_BITS;
+	void *src_buf = buf;
+	int err = -1;
 
+	/* TODO: handle sparse mode with metadata encryption */
 	if (c.sparse_mode)
 		return sparse_write_zeroed_blk(offset / F2FS_BLKSIZE,
 						len / F2FS_BLKSIZE);
@@ -598,11 +654,21 @@  int dev_fill(void *buf, __u64 offset, size_t len)
 	/* Only allow fill to zero */
 	if (*((__u8*)buf))
 		return -1;
-	if (lseek64(fd, (off64_t)offset, SEEK_SET) < 0)
+
+	buf = f2fs_metadata_crypt_blocks(buf, len, blk_addr, true);
+	if (!buf)
 		return -1;
+
+	if (lseek64(fd, (off64_t)offset, SEEK_SET) < 0)
+		goto out;
 	if (write(fd, buf, len) < 0)
-		return -1;
-	return 0;
+		goto out;
+
+	err = 0;
+out:
+	if (buf != src_buf)
+		free(buf);
+	return err;
 }
 
 int dev_fill_block(void *buf, __u64 blk_addr)
@@ -615,6 +681,11 @@  int dev_read_block(void *buf, __u64 blk_addr)
 	return dev_read(buf, blk_addr << F2FS_BLKSIZE_BITS, F2FS_BLKSIZE);
 }
 
+int dev_read_block_unencrypted(void *buf, __u64 blk_addr)
+{
+	return dev_read_unencrypted(buf, blk_addr << F2FS_BLKSIZE_BITS, F2FS_BLKSIZE);
+}
+
 int dev_reada_block(__u64 blk_addr)
 {
 	return dev_readahead(blk_addr << F2FS_BLKSIZE_BITS, F2FS_BLKSIZE);
diff --git a/mkfs/f2fs_format.c b/mkfs/f2fs_format.c
index a6c542e..bf587bf 100644
--- a/mkfs/f2fs_format.c
+++ b/mkfs/f2fs_format.c
@@ -537,6 +537,9 @@  static int f2fs_prepare_super_block(void)
 		set_sb(s_encoding_flags, c.s_encoding_flags);
 	}
 
+	if (c.metadata_crypt_key)
+		set_sb(metadata_crypt_alg, c.metadata_crypt_alg);
+
 	sb->feature = c.feature;
 
 	if (get_sb(feature) & F2FS_FEATURE_SB_CHKSUM) {
@@ -1046,7 +1049,7 @@  static int f2fs_write_super_block(void)
 	memcpy(zero_buff + F2FS_SUPER_OFFSET, sb, sizeof(*sb));
 	DBG(1, "\tWriting super block, at offset 0x%08x\n", 0);
 	for (index = 0; index < 2; index++) {
-		if (dev_write_block(zero_buff, index)) {
+		if (dev_write_block_unencrypted(zero_buff, index)) {
 			MSG(1, "\tError: While while writing super_blk "
 					"on disk!!! index : %d\n", index);
 			free(zero_buff);
diff --git a/mkfs/f2fs_format_main.c b/mkfs/f2fs_format_main.c
index f2f0a80..8856850 100644
--- a/mkfs/f2fs_format_main.c
+++ b/mkfs/f2fs_format_main.c
@@ -28,6 +28,7 @@ 
 
 #include "f2fs_fs.h"
 #include "f2fs_format_utils.h"
+#include "f2fs_metadata_crypt.h"
 
 #ifdef WITH_ANDROID
 #include <sparse/sparse.h>
@@ -44,6 +45,7 @@  static void mkfs_usage()
 	MSG(0, "\nUsage: mkfs.f2fs [options] device [sectors]\n");
 	MSG(0, "[options]:\n");
 	MSG(0, "  -a heap-based allocation [default:0]\n");
+	MSG(0, "  -A Metadata encryption algorithm\n");
 	MSG(0, "  -c device1[,device2,...] up to 7 additional devices, except meta device\n");
 	MSG(0, "  -d debug level [default:0]\n");
 	MSG(0, "  -e [cold file ext list] e.g. \"mp3,gif,mov\"\n");
@@ -54,6 +56,7 @@  static void mkfs_usage()
 	MSG(0, "  -l label\n");
 	MSG(0, "  -U uuid\n");
 	MSG(0, "  -m support zoned block device [default:0]\n");
+	MSG(0, "  -M Metadata encryption key_serial in keyring\n");
 	MSG(0, "  -o overprovision percentage [default:auto]\n");
 	MSG(0, "  -O feature1[,feature2,...] e.g. \"encrypt\"\n");
 	MSG(0, "  -C [encoding[:flag1,...]] Support casefolding with optional flags\n");
@@ -97,6 +100,12 @@  static void f2fs_show_info()
 					f2fs_encoding2str(c.s_encoding));
 	if (c.feature & le32_to_cpu(F2FS_FEATURE_PRJQUOTA))
 		MSG(0, "Info: Enable Project quota\n");
+
+	if (c.metadata_crypt_key)
+		MSG(0, "Info: Metadata key is %s\n", c.metadata_crypt_key);
+
+	if (c.metadata_crypt_alg)
+		MSG(0, "Info: Metadata alg is %d\n", c.metadata_crypt_alg);
 }
 
 static void add_default_options(void)
@@ -125,7 +134,7 @@  static void add_default_options(void)
 
 static void f2fs_parse_options(int argc, char *argv[])
 {
-	static const char *option_string = "qa:c:C:d:e:E:g:il:mo:O:rR:s:S:z:t:T:U:Vfw:";
+	static const char *option_string = "qa:A:c:C:d:e:E:g:il:mM:o:O:rR:s:S:z:t:T:U:Vfw:";
 	int32_t option=0;
 	int val;
 	char *token;
@@ -138,6 +147,14 @@  static void f2fs_parse_options(int argc, char *argv[])
 		case 'a':
 			c.heap = atoi(optarg);
 			break;
+		case 'A':
+			c.metadata_crypt_alg = f2fs_get_crypt_alg(optarg);
+			if (c.metadata_crypt_alg < 0) {
+				MSG(0, "Error: invalid crypt algorithm specified. The choices are:");
+				f2fs_print_crypt_algs();
+				exit(1);
+			}
+			break;
 		case 'c':
 			if (c.ndevs >= MAX_DEVICES) {
 				MSG(0, "Error: Too many devices\n");
@@ -178,6 +195,12 @@  static void f2fs_parse_options(int argc, char *argv[])
 		case 'm':
 			c.zoned_mode = 1;
 			break;
+		case 'M':
+			if (f2fs_metadata_process_key(optarg)) {
+				MSG(0, "Error: Invalid metadata key\n");
+				mkfs_usage();
+			}
+			break;
 		case 'o':
 			c.overprovision = atof(optarg);
 			break;
@@ -244,6 +267,14 @@  static void f2fs_parse_options(int argc, char *argv[])
 		}
 	}
 
+	if ((!!c.metadata_crypt_key) != (!!c.metadata_crypt_alg)) {
+		MSG(0, "\tError: Both the metadata crypt key and crypt algorithm must be specified!");
+		exit(1);
+	}
+
+	if (f2fs_metadata_verify_args())
+		exit(1);
+
 	add_default_options();
 
 	if (!(c.feature & cpu_to_le32(F2FS_FEATURE_EXTRA_ATTR))) {