decrypt: AOSP 10 requires the use of fscrypt

fscrypt aosp doc: https://source.android.com/security/encryption/file-based
kernel fscrypt doc: https://www.kernel.org/doc/html/v4.18/filesystems/fscrypt.html

This commit implements the ability for TWRP to use fscrypt to decrypt
files on the fscrypt implementation. It has been implemented mostly
in a new successor library to e4crypt called libtwrpfscrypt. Most of the
code was ported from AOSP vold.

Notable updates include:
 - updated policy storage by libtar
 - lookup of fbe policies by libtwrpfscrypt
 - threaded keystore operations

Big thanks to Dees_Troy for the initial trailblazing
of encryption in TWRP.

Change-Id: I69cd2eba3693a9914e00213d4943229635d0cdae
diff --git a/libtar/Android.mk b/libtar/Android.mk
old mode 100644
new mode 100755
index 6b464f3..9a35c7b
--- a/libtar/Android.mk
+++ b/libtar/Android.mk
@@ -14,9 +14,15 @@
 LOCAL_SHARED_LIBRARIES += libselinux
 
 ifeq ($(TW_INCLUDE_CRYPTO_FBE), true)
-    LOCAL_SHARED_LIBRARIES += libe4crypt
-    LOCAL_CFLAGS += -DHAVE_EXT4_CRYPT
-    LOCAL_C_INCLUDES += $(LOCAL_PATH)/../crypto/ext4crypt
+    ifeq ($(shell test $(PLATFORM_SDK_VERSION) -ge 29; echo $$?),0)
+        LOCAL_SHARED_LIBRARIES += libtwrpfscrypt
+        LOCAL_CFLAGS += -DUSE_FSCRYPT
+        LOCAL_C_INCLUDES += $(LOCAL_PATH)/../crypto/fscrypt
+    else
+        LOCAL_SHARED_LIBRARIES += libe4crypt
+        LOCAL_CFLAGS += -DHAVE_EXT4_CRYPT
+        LOCAL_C_INCLUDES += $(LOCAL_PATH)/../crypto/ext4crypt
+    endif
 endif
 
 include $(BUILD_SHARED_LIBRARY)
@@ -35,9 +41,15 @@
 LOCAL_STATIC_LIBRARIES += libselinux
 
 ifeq ($(TW_INCLUDE_CRYPTO_FBE), true)
-    LOCAL_SHARED_LIBRARIES += libe4crypt
-    LOCAL_CFLAGS += -DHAVE_EXT4_CRYPT
-    LOCAL_C_INCLUDES += $(LOCAL_PATH)/../crypto/ext4crypt
+    ifeq ($(shell test $(PLATFORM_SDK_VERSION) -ge 29; echo $$?),0)
+        LOCAL_SHARED_LIBRARIES += libtwrpfscrypt
+        LOCAL_CFLAGS += -DUSE_FSCRYPT
+        LOCAL_C_INCLUDES += $(LOCAL_PATH)/../crypto/fscrypt
+    else
+        LOCAL_SHARED_LIBRARIES += libe4crypt
+        LOCAL_CFLAGS += -DHAVE_EXT4_CRYPT
+        LOCAL_C_INCLUDES += $(LOCAL_PATH)/../crypto/ext4crypt
+    endif
 endif
 
 include $(BUILD_STATIC_LIBRARY)
diff --git a/libtar/append.c b/libtar/append.c
old mode 100644
new mode 100755
index 8f09de2..3075a61
--- a/libtar/append.c
+++ b/libtar/append.c
@@ -24,22 +24,28 @@
 
 #include <sys/capability.h>
 #include <sys/xattr.h>
+#include <linux/fs.h>
 #include <linux/xattr.h>
 
 #ifdef STDC_HEADERS
-# include <stdlib.h>
-# include <string.h>
+#include <stdlib.h>
+#include <string.h>
 #endif
 
 #ifdef HAVE_UNISTD_H
-# include <unistd.h>
+#include <unistd.h>
 #endif
 
 #include <selinux/selinux.h>
 
 #ifdef HAVE_EXT4_CRYPT
-# include "ext4crypt_tar.h"
+#include "ext4crypt_tar.h"
 #endif
+
+#ifdef USE_FSCRYPT
+#include "fscrypt_policy.h"
+#endif
+
 #include "android_utils.h"
 
 struct tar_dev
@@ -142,6 +148,7 @@
 			printf("malloc ext4_encryption_policy\n");
 			return -1;
 		}
+
 		if (e4crypt_policy_get_struct(realname, t->th_buf.eep))
 		{
 			char tar_policy[EXT4_KEY_DESCRIPTOR_SIZE];
@@ -166,6 +173,43 @@
 		}
 	}
 #endif
+#ifdef USE_FSCRYPT
+	if (TH_ISDIR(t) && t->options & TAR_STORE_FSCRYPT_POL)
+	{
+		if (t->th_buf.fep != NULL)
+		{
+			free(t->th_buf.fep);
+			t->th_buf.fep = NULL;
+		}
+
+		t->th_buf.fep = (struct fscrypt_encryption_policy*)malloc(sizeof(struct fscrypt_encryption_policy));
+		if (!t->th_buf.fep) {
+			printf("malloc fs_encryption_policy\n");
+			return -1;
+		}
+
+		if (fscrypt_policy_get_struct(realname, t->th_buf.fep)) {
+			uint8_t tar_policy[FS_KEY_DESCRIPTOR_SIZE];
+			memset(tar_policy, 0, sizeof(tar_policy));
+			char policy_hex[FS_KEY_DESCRIPTOR_SIZE_HEX];
+			policy_to_hex(t->th_buf.fep->master_key_descriptor, policy_hex);
+			if (lookup_ref_key(t->th_buf.fep->master_key_descriptor, &tar_policy[0])) {
+				printf("found fscrypt policy '%s' - '%s' - '%s'\n", realname, tar_policy, policy_hex);
+				memcpy(t->th_buf.fep->master_key_descriptor, tar_policy, FS_KEY_DESCRIPTOR_SIZE);
+			} else {
+				printf("failed to lookup fscrypt tar policy for '%s' - '%s'\n", realname, policy_hex);
+				free(t->th_buf.fep);
+				t->th_buf.fep = NULL;
+				return -1;
+			}
+		}
+		else {
+			// no policy found, but this is not an error as not all dirs will have a policy
+			free(t->th_buf.fep);
+			t->th_buf.fep = NULL;
+		}
+	}
+#endif
 
 	/* get posix file capabilities */
 	if (TH_ISREG(t) && t->options & TAR_STORE_POSIX_CAP)
diff --git a/libtar/block.c b/libtar/block.c
old mode 100644
new mode 100755
index 834c164..2f1004b
--- a/libtar/block.c
+++ b/libtar/block.c
@@ -18,8 +18,14 @@
 # include <stdlib.h>
 #endif
 
+#define DEBUG 1
+
 #ifdef HAVE_EXT4_CRYPT
-# include "ext4crypt_tar.h"
+#include "ext4crypt_tar.h"
+#endif
+
+#ifdef USE_FSCRYPT
+#include "fscrypt_policy.h"
 #endif
 
 #define BIT_ISSET(bitmask, bit) ((bitmask) & (bit))
@@ -33,6 +39,10 @@
 #define E4CRYPT_TAG "TWRP.security.e4crypt="
 #define E4CRYPT_TAG_LEN strlen(E4CRYPT_TAG)
 
+// Used to identify fscrypt_policy in extended ('x')
+#define FSCRYPT_TAG "TWRP.security.fscrypt="
+#define FSCRYPT_TAG_LEN strlen(FSCRYPT_TAG)
+
 // Used to identify Posix capabilities in extended ('x')
 #define CAPABILITIES_TAG "SCHILY.xattr.security.capability="
 #define CAPABILITIES_TAG_LEN strlen(CAPABILITIES_TAG)
@@ -132,7 +142,7 @@
 	char *ptr;
 
 #ifdef DEBUG
-	printf("==> th_read(t=0x%lx)\n", t);
+	printf("==> th_read(t=0x%p)\n", (void *)t);
 #endif
 
 	if (t->th_buf.gnu_longname != NULL)
@@ -145,6 +155,12 @@
 	if (t->th_buf.eep != NULL)
 		free(t->th_buf.eep);
 #endif
+
+#ifdef USE_FSCRYPT
+	if (t->th_buf.fep != NULL)
+		free(t->th_buf.fep);
+#endif
+
 	if (t->th_buf.has_cap_data)
 	{
 		memset(&t->th_buf.cap_data, 0, sizeof(struct vfs_cap_data));
@@ -178,7 +194,7 @@
 		}
 #ifdef DEBUG
 		printf("    th_read(): GNU long linkname detected "
-		       "(%ld bytes, %d blocks)\n", sz, blocks);
+		       "(%zu bytes, %zu blocks)\n", sz, blocks);
 #endif
 		t->th_buf.gnu_longlink = (char *)malloc(blocks * T_BLOCKSIZE);
 		if (t->th_buf.gnu_longlink == NULL)
@@ -189,7 +205,7 @@
 		{
 #ifdef DEBUG
 			printf("    th_read(): reading long linkname "
-			       "(%d blocks left, ptr == %ld)\n", blocks-j, ptr);
+			       "(%zu blocks left, ptr == %p)\n", blocks-j, (void *) ptr);
 #endif
 			i = tar_block_read(t, ptr);
 			if (i != T_BLOCKSIZE)
@@ -228,7 +244,7 @@
 		}
 #ifdef DEBUG
 		printf("    th_read(): GNU long filename detected "
-		       "(%ld bytes, %d blocks)\n", sz, blocks);
+		       "(%zu bytes, %zu blocks)\n", sz, blocks);
 #endif
 		t->th_buf.gnu_longname = (char *)malloc(blocks * T_BLOCKSIZE);
 		if (t->th_buf.gnu_longname == NULL)
@@ -239,7 +255,7 @@
 		{
 #ifdef DEBUG
 			printf("    th_read(): reading long filename "
-			       "(%d blocks left, ptr == %ld)\n", blocks-j, ptr);
+			       "(%zu blocks left, ptr == %p)\n", blocks-j, (void *) ptr);
 #endif
 			i = tar_block_read(t, ptr);
 			if (i != T_BLOCKSIZE)
@@ -266,7 +282,7 @@
 		}
 	}
 
-	// Extended headers (selinux contexts, posix file capabilities, ext4 encryption policies)
+	// Extended headers (selinux contexts, posix file capabilities and encryption policies)
 	while(TH_ISEXTHEADER(t) || TH_ISPOLHEADER(t))
 	{
 		sz = th_get_size(t);
@@ -386,6 +402,36 @@
 				}
 			}
 #endif // HAVE_EXT4_CRYPT
+
+#ifdef USE_FSCRYPT
+			start = strstr(buf, FSCRYPT_TAG);
+			if (start && start+FSCRYPT_TAG_LEN < buf+len) {
+				t->th_buf.fep = (struct fscrypt_encryption_policy*)malloc(sizeof(struct fscrypt_encryption_policy));
+				if (!t->th_buf.fep) {
+					printf("malloc fscrypt_encryption_policy\n");
+					return -1;
+				}
+				start += FSCRYPT_TAG_LEN;
+				if (*start == '0') {
+					start++;
+					char *newline_check = start + sizeof(struct fscrypt_encryption_policy);
+					if (*newline_check != '\n')
+						printf("did not find newline char in expected location, continuing anyway...\n");
+					memcpy(t->th_buf.fep, start, sizeof(struct fscrypt_encryption_policy));
+#ifdef DEBUG
+					printf("    th_read(): FSCrypt policy v1 detected: %i %i %i %i %s\n",
+						(int)t->th_buf.fep->version,
+						(int)t->th_buf.fep->contents_encryption_mode,
+						(int)t->th_buf.fep->filenames_encryption_mode,
+						(int)t->th_buf.fep->flags,
+						t->th_buf.fep->master_key_descriptor);
+#endif
+				}
+				else {
+					printf("     invalid fscrypt header found\n");
+				}
+			}
+#endif // USE_FSCRYPT
 		}
 
 		i = th_read_internal(t);
@@ -616,6 +662,38 @@
 	}
 #endif
 
+#ifdef USE_FSCRYPT
+	if((t->options & TAR_STORE_FSCRYPT_POL) && t->th_buf.fep != NULL)
+	{
+#ifdef DEBUG
+		printf("th_write(): using fscrypt_policy %s\n",
+		       t->th_buf.fep->master_key_descriptor);
+#endif
+		/* setup size - EXT header has format "*size of this whole tag as ascii numbers* *space* *version code* *content* *newline* */
+		//                                                       size   newline
+		sz = FSCRYPT_TAG_LEN + sizeof(struct fscrypt_encryption_policy) + 1 + 3  +    1;
+
+		if(sz >= 100) // another ascci digit for size
+			++sz;
+
+		if (total_sz + sz >= T_BLOCKSIZE)
+		{
+			if (th_write_extended(t, &buf[0], total_sz))
+				return -1;
+			ptr = buf;
+			total_sz = sz;
+		}
+		else
+			total_sz += sz;
+
+		snprintf(ptr, T_BLOCKSIZE, "%d "FSCRYPT_TAG"0", (int)sz);
+		memcpy(ptr + sz - sizeof(struct fscrypt_encryption_policy) - 1, t->th_buf.fep, sizeof(struct fscrypt_encryption_policy));
+		char *nlptr = ptr + sz - 1;
+		*nlptr = '\n';
+		ptr += sz;
+	}
+#endif
+
 	if((t->options & TAR_STORE_POSIX_CAP) && t->th_buf.has_cap_data)
 	{
 #ifdef DEBUG
diff --git a/libtar/extract.c b/libtar/extract.c
old mode 100644
new mode 100755
index ea86c23..fd5e3c9
--- a/libtar/extract.c
+++ b/libtar/extract.c
@@ -36,8 +36,13 @@
 #include <selinux/selinux.h>
 
 #ifdef HAVE_EXT4_CRYPT
-# include "ext4crypt_tar.h"
+#include "ext4crypt_tar.h"
 #endif
+
+#ifdef USE_FSCRYPT
+#include "fscrypt_policy.h"
+#endif
+
 #include "android_utils.h"
 
 const unsigned long long progress_size = (unsigned long long)(T_BLOCKSIZE);
@@ -572,6 +577,31 @@
 	}
 #endif
 
+#ifdef USE_FSCRYPT
+	if(t->th_buf.fep != NULL)
+	{
+#ifdef DEBUG
+		printf("tar_extract_file(): restoring fscrypt policy %s to dir %s\n", t->th_buf.fep->master_key_descriptor, realname);
+#endif
+		uint8_t binary_policy[FS_KEY_DESCRIPTOR_SIZE];
+		if (!lookup_ref_tar(t->th_buf.fep->master_key_descriptor, &binary_policy[0])) {
+			printf("error looking up proper fscrypt policy for '%s' - %s\n", realname, t->th_buf.fep->master_key_descriptor);
+			return -1;
+		}
+		char policy_hex[FS_KEY_DESCRIPTOR_SIZE_HEX];
+		policy_to_hex(binary_policy, policy_hex);
+		printf("restoring policy %s > '%s' to '%s'\n", t->th_buf.fep->master_key_descriptor, policy_hex, realname);
+		memcpy(&t->th_buf.fep->master_key_descriptor, binary_policy, FS_KEY_DESCRIPTOR_SIZE);
+		if (!fscrypt_policy_set_struct(realname, t->th_buf.fep))
+		{
+			printf("tar_extract_file(): failed to restore fscrypt policy to dir '%s' '%s'!!!\n", realname, policy_hex);
+			//return -1; // This may not be an error in some cases, so log and ignore
+		}
+	}
+	else
+		printf("NULL FSCRYPT\n");
+#endif
+
 	return 0;
 }
 
diff --git a/libtar/libtar.h b/libtar/libtar.h
old mode 100644
new mode 100755
index aa637b1..19ddd06
--- a/libtar/libtar.h
+++ b/libtar/libtar.h
@@ -24,6 +24,10 @@
 # include "ext4crypt_tar.h"
 #endif
 
+#ifdef USE_FSCRYPT
+#include "fscrypt_policy.h"
+#endif
+
 #ifdef __cplusplus
 extern "C"
 {
@@ -71,6 +75,9 @@
 #ifdef HAVE_EXT4_CRYPT
 	struct ext4_encryption_policy *eep;
 #endif
+#ifdef USE_FSCRYPT
+	struct fscrypt_encryption_policy *fep;
+#endif
 	int has_cap_data;
 	struct vfs_cap_data cap_data;
 	int has_user_default;
@@ -120,7 +127,12 @@
 #define TAR_IGNORE_CRC		64	/* ignore CRC in file header */
 #define TAR_STORE_SELINUX	128	/* store selinux context */
 #define TAR_USE_NUMERIC_ID	256	/* favor numeric owner over names */
+#ifdef HAVE_EXT4_CRYPT
 #define TAR_STORE_EXT4_POL	512	/* store ext4 crypto policy */
+#endif
+#ifdef USE_FSCRYPT
+#define TAR_STORE_FSCRYPT_POL 512 /* store fscrypt crypto policy */
+#endif
 #define TAR_STORE_POSIX_CAP	1024	/* store posix file capabilities */
 #define TAR_STORE_ANDROID_USER_XATTR	2048	/* store android user.* xattr */
 
diff --git a/libtar/output.c b/libtar/output.c
old mode 100644
new mode 100755
index f5431b6..68c3e5f
--- a/libtar/output.c
+++ b/libtar/output.c
@@ -25,9 +25,12 @@
 #endif
 
 #ifdef HAVE_EXT4_CRYPT
-# include "ext4crypt_tar.h"
+#include "ext4crypt_tar.h"
 #endif
 
+#ifdef USE_FSCRYPT
+#include "fscrypt_policy.h"
+#endif
 
 #ifndef _POSIX_LOGIN_NAME_MAX
 # define _POSIX_LOGIN_NAME_MAX	9
@@ -65,6 +68,10 @@
 	printf("  eep = \"%s\"\n",
 	       (t->th_buf.eep ? t->th_buf.eep->master_key_descriptor : "[NULL]"));
 #endif
+#ifdef USE_FSCRYPT
+	printf("  fep = \"%s\"\n",
+	       (t->th_buf.fep ? t->th_buf.fep->master_key_descriptor : (uint8_t*) "[NULL]"));
+#endif
 }