This patch changes the way eCryptfs headers use tag 3 (symmetric key) and tag 1 (asymmetric key) packets, adding fields to store cipher mode while keeping the format byte compatible with older version of eCryptfs when CBC mode is used. For tag 1 packets, the 1 byte cipher identifier field used to always contain a constant value that was ignored when reading in the header. This has been repurposed to hold the cipher mode code. For tag 3 packets, an extra cipher mode field has been added, which will only be present if the cipher mode used is other than CBC. In both cases, the tag version number is incremented when a packet is written that is not byte compatible with older versions of eCryptfs. This happens whenever CBC mode is not used. Signed-off-by: Alvin Tran <althaitran@xxxxxxxxx> Signed-off-by: Michael Chang <thenewme91@xxxxxxxxx> Signed-off-by: William Morrison <camocrazed@xxxxxxxxx> Signed-off-by: Zameer Manji <zmanji@xxxxxxxxx> --- fs/ecryptfs/crypto.c | 61 +++++++++++++++ fs/ecryptfs/ecryptfs_kernel.h | 2 + fs/ecryptfs/keystore.c | 164 +++++++++++++++++++++++++++++++++++------ include/linux/ecryptfs.h | 2 + 4 files changed, 208 insertions(+), 21 deletions(-) diff --git a/fs/ecryptfs/crypto.c b/fs/ecryptfs/crypto.c index 995b324..3e4fcb9 100644 --- a/fs/ecryptfs/crypto.c +++ b/fs/ecryptfs/crypto.c @@ -964,6 +964,67 @@ void ecryptfs_write_crypt_stat_flags(char *page_virt, (*written) = 4; } +struct ecryptfs_cipher_mode_code_str_map_elem { + char mode_str[ECRYPTFS_MAX_CIPHER_MODE_NAME_SIZE]; + u8 mode_code; +}; + +static struct ecryptfs_cipher_mode_code_str_map_elem +ecryptfs_cipher_mode_code_str_map[] = { + {"cbc", ECRYPTFS_CIPHER_MODE_CBC}, +}; + +/** + * ecryptfs_code_for_cipher_mode_string + * @mode_name: The string alias for the cipher mode + * + * Retruns zero on no match, or the cipher code on match + */ +u8 ecryptfs_code_for_cipher_mode_string(char *mode_name) +{ + int i; + u8 code = 0; + struct ecryptfs_cipher_mode_code_str_map_elem *map = + ecryptfs_cipher_mode_code_str_map; + + for (i = 0; i < ARRAY_SIZE(ecryptfs_cipher_mode_code_str_map); i++) + if (strcmp(mode_name, map[i].mode_str) == 0) { + code = map[i].mode_code; + break; + } + + return code; +} + +/** + * ecryptfs_cipher_mode_code_to_string + * @str: Destination to write out the cipher mode name + * @cipher_code: The code to conver to cipher mode name string + * + * Retruns zero in success + */ +int ecryptfs_cipher_mode_code_to_string(char *str, u8 mode_code) +{ + int rc = 0; + int i; + struct ecryptfs_cipher_mode_code_str_map_elem *map = + ecryptfs_cipher_mode_code_str_map; + + str[0] = '\0'; + for (i = 0; i < ARRAY_SIZE(ecryptfs_cipher_mode_code_str_map); i++) + if (mode_code == map[i].mode_code) { + strcpy(str, map[i].mode_str); + break; + } + if (str[0] == '\0') { + ecryptfs_printk(KERN_WARNING, "Cipher mode not recognized: " + "[%d]\n", mode_code); + rc = -EINVAL; + } + + return rc; +} + struct ecryptfs_cipher_code_str_map_elem { char cipher_str[16]; u8 cipher_code; diff --git a/fs/ecryptfs/ecryptfs_kernel.h b/fs/ecryptfs/ecryptfs_kernel.h index dae2e1d..392f710 100644 --- a/fs/ecryptfs/ecryptfs_kernel.h +++ b/fs/ecryptfs/ecryptfs_kernel.h @@ -604,6 +604,8 @@ int ecryptfs_read_and_validate_xattr_region(struct dentry *dentry, struct inode *inode); u8 ecryptfs_code_for_cipher_string(char *cipher_name, size_t key_bytes); int ecryptfs_cipher_code_to_string(char *str, u8 cipher_code); +u8 ecryptfs_code_for_cipher_mode_string(char *mode_name); +int ecryptfs_cipher_mode_code_to_string(char *str, u8 mode_code); void ecryptfs_set_default_sizes(struct ecryptfs_crypt_stat *crypt_stat); int ecryptfs_generate_key_packet_set(char *dest_base, struct ecryptfs_crypt_stat *crypt_stat, diff --git a/fs/ecryptfs/keystore.c b/fs/ecryptfs/keystore.c index 4725a07..682d26f 100644 --- a/fs/ecryptfs/keystore.c +++ b/fs/ecryptfs/keystore.c @@ -1248,6 +1248,7 @@ parse_tag_1_packet(struct ecryptfs_crypt_stat *crypt_stat, size_t body_size; struct ecryptfs_auth_tok_list_item *auth_tok_list_item; size_t length_size; + u8 tag_version; int rc = 0; (*packet_size) = 0; @@ -1260,7 +1261,7 @@ parse_tag_1_packet(struct ecryptfs_crypt_stat *crypt_stat, * Max Tag 1 packet size (max 3 bytes) * Version (1 byte) * Key identifier (8 bytes; ECRYPTFS_SIG_SIZE) - * Cipher identifier (1 byte) + * Cipher mode identifier (1 byte) (ignored if packet version is 0x03) * Encrypted key size (arbitrary) * * 12 bytes minimum packet size @@ -1305,18 +1306,30 @@ parse_tag_1_packet(struct ecryptfs_crypt_stat *crypt_stat, rc = -EINVAL; goto out_free; } - if (unlikely(data[(*packet_size)++] != 0x03)) { + tag_version = data[(*packet_size)++]; + if (unlikely(tag_version != 0x03 && tag_version != 0x04)) { printk(KERN_WARNING "Unknown version number [%d]\n", - data[(*packet_size) - 1]); + tag_version); rc = -EINVAL; goto out_free; } ecryptfs_to_hex((*new_auth_tok)->token.private_key.signature, &data[(*packet_size)], ECRYPTFS_SIG_SIZE); *packet_size += ECRYPTFS_SIG_SIZE; - /* This byte is skipped because the kernel does not need to - * know which public key encryption algorithm was used */ - (*packet_size)++; + if (tag_version == 0x03) { + /* This byte is skipped because the kernel does not need to + * know which public key encryption algorithm was used */ + (*packet_size)++; + strcpy(crypt_stat->cipher_mode, "cbc"); + } else { + /* This field is repurposed in packet version 0x04 to hold the + * cipher mode. It is ignored in earlier packet versions. */ + rc = ecryptfs_cipher_mode_code_to_string( + crypt_stat->cipher_mode, + data[(*packet_size)++]); + if (rc) + goto out_free; + } (*new_auth_tok)->session_key.encrypted_key_size = body_size - (ECRYPTFS_SIG_SIZE + 2); if ((*new_auth_tok)->session_key.encrypted_key_size @@ -1380,18 +1393,34 @@ parse_tag_3_packet(struct ecryptfs_crypt_stat *crypt_stat, size_t body_size; struct ecryptfs_auth_tok_list_item *auth_tok_list_item; size_t length_size; + size_t min_body_size; + u8 file_version; int rc = 0; (*packet_size) = 0; (*new_auth_tok) = NULL; - /** - *This format is inspired by OpenPGP; see RFC 2440 + /* This format is inspired by OpenPGP; see RFC 2440 * packet tag 3 * + * This packet format deviates from the RFC in two ways: + * 1. There is an additional field describing which cipher + * mode was used. + * + * 2. The version field is used to differentiate between different + * versions of the packet as used in eCryptfs instead of the different + * version of tag 3 in the RFC. + * + * Version 0x04 is a packet that does not have the additional + * cipher mode field. The cipher mode is then assumed to be CBC. + * + * Version 0x05 is a packet that does have the additional cipher mode + * field. The cipher mode is taken from this header. + * * Tag 3 identifier (1 byte) * Max Tag 3 packet size (max 3 bytes) * Version (1 byte) * Cipher code (1 byte) + * Cipher mode code (1 byte) (if version >= 0x05) * S2K specifier (1 byte) * Hash identifier (1 byte) * Salt (ECRYPTFS_SALT_SIZE) @@ -1400,6 +1429,16 @@ parse_tag_3_packet(struct ecryptfs_crypt_stat *crypt_stat, * * (ECRYPTFS_SALT_SIZE + 7) minimum packet size */ + + /* Holds the minimum amount of data for a version 0x04 packet */ + min_body_size = (1 /* Version */ + + 1 /* Cipher Code */ + + 1 /* S2K Specifier */ + + 1 /* Hash Identifier */ + + ECRYPTFS_SALT_SIZE + + 1 /* Hash Iterations */ + ); + if (max_packet_size < (ECRYPTFS_SALT_SIZE + 7)) { printk(KERN_ERR "Max packet size too large\n"); rc = -EINVAL; @@ -1428,7 +1467,7 @@ parse_tag_3_packet(struct ecryptfs_crypt_stat *crypt_stat, rc); goto out_free; } - if (unlikely(body_size < (ECRYPTFS_SALT_SIZE + 5))) { + if (unlikely(body_size < min_body_size)) { printk(KERN_WARNING "Invalid body size ([%td])\n", body_size); rc = -EINVAL; goto out_free; @@ -1439,8 +1478,19 @@ parse_tag_3_packet(struct ecryptfs_crypt_stat *crypt_stat, rc = -EINVAL; goto out_free; } + file_version = data[(*packet_size)++]; + if (unlikely(file_version != 0x04 && file_version != 0x05)) { + printk(KERN_WARNING "Unknown version number [%d]\n", + file_version); + rc = -EINVAL; + goto out_free; + } + if (file_version != 0x04) { + /* This file version has an extra byte for cipher mode code */ + min_body_size += 1; + } (*new_auth_tok)->session_key.encrypted_key_size = - (body_size - (ECRYPTFS_SALT_SIZE + 5)); + (body_size - min_body_size); if ((*new_auth_tok)->session_key.encrypted_key_size > ECRYPTFS_MAX_ENCRYPTED_KEY_BYTES) { printk(KERN_WARNING "Tag 3 packet contains key larger " @@ -1448,12 +1498,6 @@ parse_tag_3_packet(struct ecryptfs_crypt_stat *crypt_stat, rc = -EINVAL; goto out_free; } - if (unlikely(data[(*packet_size)++] != 0x04)) { - printk(KERN_WARNING "Unknown version number [%d]\n", - data[(*packet_size) - 1]); - rc = -EINVAL; - goto out_free; - } rc = ecryptfs_cipher_code_to_string(crypt_stat->cipher, (u16)data[(*packet_size)]); if (rc) @@ -1468,6 +1512,18 @@ parse_tag_3_packet(struct ecryptfs_crypt_stat *crypt_stat, crypt_stat->key_size = (*new_auth_tok)->session_key.encrypted_key_size; } + + /* Read the cipher mode if it is present */ + if (file_version != 0x04) { + rc = ecryptfs_cipher_mode_code_to_string( + crypt_stat->cipher_mode, + data[(*packet_size)++]); + if (rc) + goto out_free; + } else { + strcpy(crypt_stat->cipher_mode, "cbc"); + } + rc = ecryptfs_init_crypt_ctx(crypt_stat); if (rc) goto out_free; @@ -2031,6 +2087,7 @@ write_tag_1_packet(char *dest, size_t *remaining_bytes, size_t encrypted_session_key_valid = 0; size_t packet_size_length; size_t max_packet_size; + u8 cipher_mode_code; int rc = 0; (*packet_size) = 0; @@ -2069,7 +2126,7 @@ encrypted_session_key_set: + 3 /* Max Tag 1 packet size */ + 1 /* Version */ + ECRYPTFS_SIG_SIZE /* Key identifier */ - + 1 /* Cipher identifier */ + + 1 /* Cipher mode code */ + key_rec->enc_key_size); /* Encrypted key size */ if (max_packet_size > (*remaining_bytes)) { printk(KERN_ERR "Packet length larger than maximum allowable; " @@ -2088,10 +2145,29 @@ encrypted_session_key_set: goto out; } (*packet_size) += packet_size_length; - dest[(*packet_size)++] = 0x03; /* version 3 */ + + cipher_mode_code = + ecryptfs_code_for_cipher_mode_string(crypt_stat->cipher_mode); + if (cipher_mode_code == 0) { + ecryptfs_printk(KERN_WARNING, "Unable to generate code for " + "cipher mode [%s]\n", crypt_stat->cipher_mode); + rc = -EINVAL; + goto out; + } + + /* Tag Version */ + if (cipher_mode_code == ECRYPTFS_CIPHER_MODE_CBC) + dest[(*packet_size)++] = 0x03; + else + dest[(*packet_size)++] = 0x04; memcpy(&dest[(*packet_size)], key_rec->sig, ECRYPTFS_SIG_SIZE); (*packet_size) += ECRYPTFS_SIG_SIZE; - dest[(*packet_size)++] = RFC2440_CIPHER_RSA; + if (cipher_mode_code == ECRYPTFS_CIPHER_MODE_CBC) { + /* Version 0x03 packets always contain this constant */ + dest[(*packet_size)++] = RFC2440_CIPHER_RSA; + } else { + dest[(*packet_size)++] = cipher_mode_code; + } memcpy(&dest[(*packet_size)], key_rec->enc_key, key_rec->enc_key_size); (*packet_size) += key_rec->enc_key_size; @@ -2189,6 +2265,7 @@ write_tag_3_packet(char *dest, size_t *remaining_bytes, struct scatterlist src_sg[2]; struct mutex *tfm_mutex = NULL; u8 cipher_code; + u8 cipher_mode_code; size_t packet_size_length; size_t max_packet_size; struct ecryptfs_mount_crypt_stat *mount_crypt_stat = @@ -2313,7 +2390,39 @@ write_tag_3_packet(char *dest, size_t *remaining_bytes, } encrypted_session_key_set: /* This format is inspired by OpenPGP; see RFC 2440 - * packet tag 3 */ + * packet tag 3 + * + * This packet format deviates from the RFC in two ways: + * 1. There is an additional field describing which cipher + * mode was used. + * + * 2. The version field is used to differentiate between different + * versions of the packet as used in eCryptfs instead of the different + * version of tag 3 in the RFC. + * + * Version 0x04 is a packet that does not have the additional + * cipher mode field. The cipher mode is then assumed to be CBC. + * + * Version 0x05 is a packet that does have the additional cipher mode + * field. The cipher mode is taken from this header. + * + * This is for forwards compatability, so that older versions of + * eCryptfs can still read and write CBC encrypted files from new + * versions, but will refuse to open files encrypted in modes they do + * not understand. + * + * */ + + cipher_mode_code = + ecryptfs_code_for_cipher_mode_string(crypt_stat->cipher_mode); + + if (cipher_mode_code == 0) { + ecryptfs_printk(KERN_WARNING, "Unable to generate code for " + "cipher mode [%s]\n", crypt_stat->cipher_mode); + rc = -EINVAL; + goto out; + } + max_packet_size = (1 /* Tag 3 identifier */ + 3 /* Max Tag 3 packet size */ + 1 /* Version */ @@ -2323,6 +2432,11 @@ encrypted_session_key_set: + ECRYPTFS_SALT_SIZE /* Salt */ + 1 /* Hash iterations */ + key_rec->enc_key_size); /* Encrypted key size */ + + if (cipher_mode_code != ECRYPTFS_CIPHER_MODE_CBC) { + max_packet_size += 1; /* Cipher mode code. 1 Byte */ + } + if (max_packet_size > (*remaining_bytes)) { printk(KERN_ERR "Packet too large; need up to [%td] bytes, but " "there are only [%td] available\n", max_packet_size, @@ -2342,7 +2456,12 @@ encrypted_session_key_set: goto out; } (*packet_size) += packet_size_length; - dest[(*packet_size)++] = 0x04; /* version 4 */ + /* Write out tag version */ + if (cipher_mode_code == ECRYPTFS_CIPHER_MODE_CBC) { + dest[(*packet_size)++] = 0x04; + } else { + dest[(*packet_size)++] = 0x05; + } /* TODO: Break from RFC2440 so that arbitrary ciphers can be * specified with strings */ cipher_code = ecryptfs_code_for_cipher_string(crypt_stat->cipher, @@ -2353,7 +2472,10 @@ encrypted_session_key_set: rc = -EINVAL; goto out; } + dest[(*packet_size)++] = cipher_code; + if (cipher_mode_code != ECRYPTFS_CIPHER_MODE_CBC) + dest[(*packet_size)++] = cipher_mode_code; dest[(*packet_size)++] = 0x03; /* S2K */ dest[(*packet_size)++] = 0x01; /* MD5 (TODO: parameterize) */ memcpy(&dest[(*packet_size)], auth_tok->token.password.salt, diff --git a/include/linux/ecryptfs.h b/include/linux/ecryptfs.h index 8d5ab99..8244e03 100644 --- a/include/linux/ecryptfs.h +++ b/include/linux/ecryptfs.h @@ -44,6 +44,8 @@ #define RFC2440_CIPHER_RSA 0x01 +#define ECRYPTFS_CIPHER_MODE_CBC 0x01 + /** * For convenience, we may need to pass around the encrypted session * key between kernel and userspace because the authentication token -- 1.7.10.4 -- To unsubscribe from this list: send the line "unsubscribe ecryptfs" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html