Whamcloud - gitweb
LU-16374 enc: align Base64 encoding with RFC 4648 base64url 81/49581/3
authorSebastien Buisson <sbuisson@ddn.com>
Sun, 18 Jul 2021 00:01:25 +0000 (19:01 -0500)
committerOleg Drokin <green@whamcloud.com>
Tue, 31 Jan 2023 02:34:01 +0000 (02:34 +0000)
Lustre encryption uses a Base64 encoding to encode no-key filenames
(the filenames that are presented to userspace when a directory is
listed without its encryption key).
Make this Base64 encoding compliant with RFC 4648 base64url. And use
'+' leading character to distringuish digested names.

This is adapted from kernel commit
ba47b515f594 fscrypt: align Base64 encoding with RFC 4648 base64url

To maintain compatibility with older clients, a new llite parameter
named 'filename_enc_use_old_base64' is introduced, set to 0 by
default. When 0, Lustre uses new-fashion base64 encoding. When set to
1, Lustre uses old-style base64 encoding.

To set this parameter globally for all clients, do on the MGS:
mgs# lctl set_param -P llite.*.filename_enc_use_old_base64={0,1}

Signed-off-by: Eric Biggers <ebiggers@google.com>
Signed-off-by: Sebastien Buisson <sbuisson@ddn.com>
Change-Id: Iaa2256da7fb591d842b5bb7aa474b2ee6de9899d
Reviewed-on: https://review.whamcloud.com/c/fs/lustre-release/+/49581
Tested-by: jenkins <devops@whamcloud.com>
Tested-by: Maloo <maloo@whamcloud.com>
Tested-by: jsimmons <jsimmons@infradead.org>
Reviewed-by: Andreas Dilger <adilger@whamcloud.com>
Reviewed-by: jsimmons <jsimmons@infradead.org>
Reviewed-by: Oleg Drokin <green@whamcloud.com>
libcfs/include/libcfs/crypto/llcrypt.h
libcfs/libcfs/crypto/fname.c
lustre/include/lustre_disk.h
lustre/llite/crypto.c
lustre/llite/llite_lib.c
lustre/llite/lproc_llite.c

index 893ac1e..b152915 100644 (file)
@@ -53,6 +53,8 @@ struct llcrypt_name {
 
 /* Maximum value for the third parameter of llcrypt_operations.set_context(). */
 #define LLCRYPT_SET_CONTEXT_MAX_SIZE   40
+#define LLCRYPT_DIGESTED_CHAR_OLD      '_'
+#define LLCRYPT_DIGESTED_CHAR          '+'
 
 #ifdef CONFIG_LL_ENCRYPTION
 /*
index 8b16c39..65b6b42 100644 (file)
@@ -137,10 +137,19 @@ static int fname_decrypt(struct inode *inode,
        return 0;
 }
 
+/*
+ * Old fashion base64 encoding, taken from Linux 5.4.
+ *
+ * This base64 encoding is specific to fscrypt and has been replaced since then
+ * with an RFC 4648 compliant base64-url encoding, see llcrypt_base64url_*
+ * below.
+ * The old fashion base64 encoding is kept for compatibility with older clients.
+ */
+
 static const char lookup_table[65] =
        "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,";
 
-#define BASE64_CHARS(nbytes)   DIV_ROUND_UP((nbytes) * 4, 3)
+#define LLCRYPT_BASE64_CHARS(nbytes)   DIV_ROUND_UP((nbytes) * 4, 3)
 
 /**
  * base64_encode() -
@@ -150,7 +159,7 @@ static const char lookup_table[65] =
  *
  * Return: length of the encoded string
  */
-static inbase64_encode(const u8 *src, int len, char *dst)
+static inline int llcrypt_base64_encode(const u8 *src, int len, char *dst)
 {
        int i, bits = 0, ac = 0;
        char *cp = dst;
@@ -169,7 +178,7 @@ static int base64_encode(const u8 *src, int len, char *dst)
        return cp - dst;
 }
 
-static inbase64_decode(const char *src, int len, u8 *dst)
+static inline int llcrypt_base64_decode(const char *src, int len, u8 *dst)
 {
        int i, bits = 0, ac = 0;
        const char *p;
@@ -192,6 +201,98 @@ static int base64_decode(const char *src, int len, u8 *dst)
        return cp - dst;
 }
 
+/*
+ * New fashion base64 encoding, taken from Linux 5.14.
+ *
+ * This base64 encoding is RFC 4648 compliant base64-url encoding.
+ */
+
+static const char base64url_table[] =
+       "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
+
+#define LLCRYPT_BASE64URL_CHARS(nbytes)        DIV_ROUND_UP((nbytes) * 4, 3)
+
+/**
+ * llcrypt_base64url_encode() - base64url-encode some binary data
+ * @src: the binary data to encode
+ * @srclen: the length of @src in bytes
+ * @dst: (output) the base64url-encoded string.  Not NUL-terminated.
+ *
+ * Encodes data using base64url encoding, i.e. the "Base 64 Encoding with URL
+ * and Filename Safe Alphabet" specified by RFC 4648.  '='-padding isn't used,
+ * as it's unneeded and not required by the RFC.  base64url is used instead of
+ * base64 to avoid the '/' character, which isn't allowed in filenames.
+ *
+ * Return: the length of the resulting base64url-encoded string in bytes.
+ *        This will be equal to LLCRYPT_BASE64URL_CHARS(srclen).
+ */
+static inline int llcrypt_base64url_encode(const u8 *src, int srclen, char *dst)
+{
+       u32 ac = 0;
+       int bits = 0;
+       int i;
+       char *cp = dst;
+
+       for (i = 0; i < srclen; i++) {
+               ac = (ac << 8) | src[i];
+               bits += 8;
+               do {
+                       bits -= 6;
+                       *cp++ = base64url_table[(ac >> bits) & 0x3f];
+               } while (bits >= 6);
+       }
+       if (bits)
+               *cp++ = base64url_table[(ac << (6 - bits)) & 0x3f];
+       return cp - dst;
+}
+
+/**
+ * llcrypt_base64url_decode() - base64url-decode a string
+ * @src: the string to decode.  Doesn't need to be NUL-terminated.
+ * @srclen: the length of @src in bytes
+ * @dst: (output) the decoded binary data
+ *
+ * Decodes a string using base64url encoding, i.e. the "Base 64 Encoding with
+ * URL and Filename Safe Alphabet" specified by RFC 4648.  '='-padding isn't
+ * accepted, nor are non-encoding characters such as whitespace.
+ *
+ * This implementation hasn't been optimized for performance.
+ *
+ * Return: the length of the resulting decoded binary data in bytes,
+ *        or -1 if the string isn't a valid base64url string.
+ */
+static inline int llcrypt_base64url_decode(const char *src, int srclen, u8 *dst)
+{
+       u32 ac = 0;
+       int bits = 0;
+       int i;
+       u8 *bp = dst;
+
+       for (i = 0; i < srclen; i++) {
+               const char *p = strchr(base64url_table, src[i]);
+
+               if (p == NULL || src[i] == 0)
+                       return -1;
+               ac = (ac << 6) | (p - base64url_table);
+               bits += 6;
+               if (bits >= 8) {
+                       bits -= 8;
+                       *bp++ = (u8)(ac >> bits);
+               }
+       }
+       if (ac & ((1 << bits) - 1))
+               return -1;
+       return bp - dst;
+}
+
+static inline int base64_chars(struct lustre_sb_info *lsi, int nbytes)
+{
+       if (!(lsi->lsi_flags & LSI_FILENAME_ENC_B64_OLD_CLI))
+               return LLCRYPT_BASE64URL_CHARS(nbytes);
+       else
+               return LLCRYPT_BASE64_CHARS(nbytes);
+}
+
 bool llcrypt_fname_encrypted_size(const struct inode *inode, u32 orig_len,
                                  u32 max_len, u32 *encrypted_len_ret)
 {
@@ -225,9 +326,11 @@ int llcrypt_fname_alloc_buffer(const struct inode *inode,
                               u32 max_encrypted_len,
                               struct llcrypt_str *crypto_str)
 {
+       struct lustre_sb_info *lsi = s2lsi(inode->i_sb);
        const u32 max_encoded_len =
-               max_t(u32, BASE64_CHARS(LLCRYPT_FNAME_MAX_UNDIGESTED_SIZE),
-                     1 + BASE64_CHARS(sizeof(struct llcrypt_digested_name)));
+               max_t(u32,
+                  base64_chars(lsi, LLCRYPT_FNAME_MAX_UNDIGESTED_SIZE),
+                  1 + base64_chars(lsi, sizeof(struct llcrypt_digested_name)));
        u32 max_presented_len;
 
        max_presented_len = max(max_encoded_len, max_encrypted_len);
@@ -271,6 +374,8 @@ int llcrypt_fname_disk_to_usr(struct inode *inode,
                        const struct llcrypt_str *iname,
                        struct llcrypt_str *oname)
 {
+       int (*b64_encode)(const u8 *src, int srclen, char *dst);
+       struct lustre_sb_info *lsi = s2lsi(inode->i_sb);
        const struct qstr qname = LLTR_TO_QSTR(iname);
        struct llcrypt_digested_name digested_name;
 
@@ -298,9 +403,13 @@ int llcrypt_fname_disk_to_usr(struct inode *inode,
                return 0;
        }
 
+       if (!(lsi->lsi_flags & LSI_FILENAME_ENC_B64_OLD_CLI))
+               b64_encode = llcrypt_base64url_encode;
+       else
+               b64_encode = llcrypt_base64_encode;
+
        if (iname->len <= LLCRYPT_FNAME_MAX_UNDIGESTED_SIZE) {
-               oname->len = base64_encode(iname->name, iname->len,
-                                          oname->name);
+               oname->len = b64_encode(iname->name, iname->len, oname->name);
                return 0;
        }
        if (hash) {
@@ -313,9 +422,12 @@ int llcrypt_fname_disk_to_usr(struct inode *inode,
        memcpy(digested_name.digest,
               LLCRYPT_FNAME_DIGEST(iname->name, iname->len),
               LLCRYPT_FNAME_DIGEST_SIZE);
-       oname->name[0] = '_';
-       oname->len = 1 + base64_encode((const u8 *)&digested_name,
-                                      sizeof(digested_name), oname->name + 1);
+       if (!(lsi->lsi_flags & LSI_FILENAME_ENC_B64_OLD_CLI))
+               oname->name[0] = LLCRYPT_DIGESTED_CHAR;
+       else
+               oname->name[0] = LLCRYPT_DIGESTED_CHAR_OLD;
+       oname->len = 1 + b64_encode((const u8 *)&digested_name,
+                                   sizeof(digested_name), oname->name + 1);
        return 0;
 }
 EXPORT_SYMBOL(llcrypt_fname_disk_to_usr);
@@ -347,6 +459,7 @@ EXPORT_SYMBOL(llcrypt_fname_disk_to_usr);
 int llcrypt_setup_filename(struct inode *dir, const struct qstr *iname,
                              int lookup, struct llcrypt_name *fname)
 {
+       struct lustre_sb_info *lsi = s2lsi(dir->i_sb);
        int ret;
        int digested;
 
@@ -399,14 +512,18 @@ int llcrypt_setup_filename(struct inode *dir, const struct qstr *iname,
         * We don't have the key and we are doing a lookup; decode the
         * user-supplied name
         */
-       if (iname->name[0] == '_') {
-               if (iname->len !=
-                   1 + BASE64_CHARS(sizeof(struct llcrypt_digested_name)))
+       if ((!(lsi->lsi_flags & LSI_FILENAME_ENC_B64_OLD_CLI) &&
+            iname->name[0] == LLCRYPT_DIGESTED_CHAR) ||
+           ((lsi->lsi_flags & LSI_FILENAME_ENC_B64_OLD_CLI) &&
+            iname->name[0] == LLCRYPT_DIGESTED_CHAR_OLD)) {
+               if (iname->len != 1 + base64_chars(lsi,
+                                       sizeof(struct llcrypt_digested_name))) {
                        return -ENOENT;
+               }
                digested = 1;
        } else {
                if (iname->len >
-                   BASE64_CHARS(LLCRYPT_FNAME_MAX_UNDIGESTED_SIZE))
+                   base64_chars(lsi, LLCRYPT_FNAME_MAX_UNDIGESTED_SIZE))
                        return -ENOENT;
                digested = 0;
        }
@@ -418,8 +535,15 @@ int llcrypt_setup_filename(struct inode *dir, const struct qstr *iname,
        if (fname->crypto_buf.name == NULL)
                return -ENOMEM;
 
-       ret = base64_decode(iname->name + digested, iname->len - digested,
-                           fname->crypto_buf.name);
+       if (!(lsi->lsi_flags & LSI_FILENAME_ENC_B64_OLD_CLI))
+               ret = llcrypt_base64url_decode(iname->name + digested,
+                                              iname->len - digested,
+                                              fname->crypto_buf.name);
+       else
+               ret = llcrypt_base64_decode(iname->name + digested,
+                                           iname->len - digested,
+                                           fname->crypto_buf.name);
+
        if (ret < 0) {
                ret = -ENOENT;
                goto errout;
index 753a56f..e73ce1f 100644 (file)
@@ -169,6 +169,7 @@ struct lustre_sb_info {
 #define LSI_BDI_INITIALIZED             0x00400000
 #endif
 #define LSI_FILENAME_ENC                0x00800000 /* enable name encryption */
+#define LSI_FILENAME_ENC_B64_OLD_CLI    0x01000000 /* use old style base64 */
 
 #define     s2lsi(sb)        ((struct lustre_sb_info *)((sb)->s_fs_info))
 #define     s2lsi_nocast(sb) ((sb)->s_fs_info)
index d8e8173..a832d4d 100644 (file)
@@ -252,8 +252,15 @@ int ll_setup_filename(struct inode *dir, const struct qstr *iname,
        int rc;
 
        if (fid && IS_ENCRYPTED(dir) && llcrypt_policy_has_filename_enc(dir) &&
-           !llcrypt_has_encryption_key(dir) && iname->name[0] == '_')
-               digested = 1;
+           !llcrypt_has_encryption_key(dir)) {
+               struct lustre_sb_info *lsi = s2lsi(dir->i_sb);
+
+               if ((!(lsi->lsi_flags & LSI_FILENAME_ENC_B64_OLD_CLI) &&
+                    iname->name[0] == LLCRYPT_DIGESTED_CHAR) ||
+                   ((lsi->lsi_flags & LSI_FILENAME_ENC_B64_OLD_CLI) &&
+                    iname->name[0] == LLCRYPT_DIGESTED_CHAR_OLD))
+                       digested = 1;
+       }
 
        dname.name = iname->name + digested;
        dname.len = iname->len - digested;
@@ -394,6 +401,8 @@ int ll_fname_disk_to_usr(struct inode *inode,
                if (lltr.len > LL_CRYPTO_BLOCK_SIZE * 2 &&
                    !llcrypt_has_encryption_key(inode) &&
                    llcrypt_policy_has_filename_enc(inode)) {
+                       struct lustre_sb_info *lsi = s2lsi(inode->i_sb);
+
                        digested = 1;
                        /* Without the key for long names, set the dentry name
                         * to the representing struct ll_digest_filename. It
@@ -410,7 +419,10 @@ int ll_fname_disk_to_usr(struct inode *inode,
                        lltr.name = (char *)&digest;
                        lltr.len = sizeof(digest);
 
-                       oname->name[0] = '_';
+                       if (!(lsi->lsi_flags & LSI_FILENAME_ENC_B64_OLD_CLI))
+                               oname->name[0] = LLCRYPT_DIGESTED_CHAR;
+                       else
+                               oname->name[0] = LLCRYPT_DIGESTED_CHAR_OLD;
                        oname->name = oname->name + 1;
                        oname->len--;
                }
index 114c84c..407adc3 100644 (file)
@@ -526,6 +526,7 @@ retry_connect:
                                      sbi->ll_fsname,
                                      sbi->ll_md_exp->exp_obd->obd_name);
                lsi->lsi_flags &= ~LSI_FILENAME_ENC;
+               lsi->lsi_flags &= ~LSI_FILENAME_ENC_B64_OLD_CLI;
                ll_sbi_set_name_encrypt(sbi, false);
        }
 
index b729bc6..cc224b6 100644 (file)
@@ -1687,6 +1687,53 @@ static ssize_t ll_filename_enc_seq_write(struct file *file,
 }
 
 LDEBUGFS_SEQ_FOPS(ll_filename_enc);
+
+static int ll_old_b64_enc_seq_show(struct seq_file *m, void *v)
+{
+       struct super_block *sb = m->private;
+       struct lustre_sb_info *lsi = s2lsi(sb);
+
+       seq_printf(m, "%u\n",
+                  lsi->lsi_flags & LSI_FILENAME_ENC_B64_OLD_CLI ? 1 : 0);
+       return 0;
+}
+
+static ssize_t ll_old_b64_enc_seq_write(struct file *file,
+                                        const char __user *buffer,
+                                        size_t count, loff_t *off)
+{
+       struct seq_file *m = file->private_data;
+       struct super_block *sb = m->private;
+       struct lustre_sb_info *lsi = s2lsi(sb);
+       struct ll_sb_info *sbi = ll_s2sbi(sb);
+       bool val;
+       int rc;
+
+       rc = kstrtobool_from_user(buffer, count, &val);
+       if (rc)
+               return rc;
+
+       if (val) {
+               if (!ll_sbi_has_name_encrypt(sbi)) {
+                       /* server does not support name encryption,
+                        * so force it to NULL on client
+                        */
+                       CDEBUG(D_SEC,
+                              "%s: server does not support name encryption\n",
+                              sbi->ll_fsname);
+                       lsi->lsi_flags &= ~LSI_FILENAME_ENC_B64_OLD_CLI;
+                       return -EOPNOTSUPP;
+               }
+
+               lsi->lsi_flags |= LSI_FILENAME_ENC_B64_OLD_CLI;
+       } else {
+               lsi->lsi_flags &= ~LSI_FILENAME_ENC_B64_OLD_CLI;
+       }
+
+       return count;
+}
+
+LDEBUGFS_SEQ_FOPS(ll_old_b64_enc);
 #endif /* CONFIG_LL_ENCRYPTION */
 
 static int ll_pcc_seq_show(struct seq_file *m, void *v)
@@ -1746,6 +1793,8 @@ struct ldebugfs_vars lprocfs_llite_obd_vars[] = {
 #ifdef CONFIG_LL_ENCRYPTION
        { .name =       "enable_filename_encryption",
          .fops =       &ll_filename_enc_fops,                  },
+       { .name =       "filename_enc_use_old_base64",
+         .fops =       &ll_old_b64_enc_fops,                   },
 #endif
        { NULL }
 };