Add the key derivation algorithm used with PKCS#12 to pkcs5.c so that it
can be found together with the two PKCS#5 KDFs, and so that it can also
be used when parsing of the PKCS#12 AlgorithmIdentifiers in the next
commit. This KDF is not recommended for new uses.
---
ell/pkcs5-private.h | 12 ++++
ell/pkcs5.c | 146 ++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 158 insertions(+)
diff --git a/ell/pkcs5-private.h b/ell/pkcs5-private.h
index 27ff41c..9b85fdd 100644
--- a/ell/pkcs5-private.h
+++ b/ell/pkcs5-private.h
@@ -20,6 +20,18 @@
*
*/
+struct pkcs12_hash {
+ enum l_checksum_type alg;
+ unsigned int len;
+ unsigned int u;
+ unsigned int v;
+ struct asn1_oid oid;
+};
+
+uint8_t *pkcs12_pbkdf(const char *password, const struct pkcs12_hash *hash,
+ const uint8_t *salt, size_t salt_len,
+ unsigned int iterations, uint8_t id, size_t key_len);
+
struct l_cipher *pkcs5_cipher_from_alg_id(const uint8_t *id_asn1,
size_t id_asn1_len,
const char *password,
diff --git a/ell/pkcs5.c b/ell/pkcs5.c
index 21fda2b..bb02c33 100644
--- a/ell/pkcs5.c
+++ b/ell/pkcs5.c
@@ -33,6 +33,7 @@
#include "checksum.h"
#include "cipher.h"
#include "util.h"
+#include "utf8.h"
#include "asn1-private.h"
#include "pkcs5.h"
#include "pkcs5-private.h"
@@ -182,14 +183,159 @@ LIB_EXPORT bool l_pkcs5_pbkdf2(enum l_checksum_type type, const
char *password,
return !dk_len;
}
+static bool utf8_to_bmpstring(const char *utf, uint8_t *out_buf,
+ unsigned int *out_len)
+{
+ unsigned int n;
+ int utflen;
+ wchar_t cp;
+ uint16_t *ptr = (uint16_t *) out_buf;
+
+ for (n = strlen(utf); n; n -= utflen, utf += utflen) {
+ if ((utflen = l_utf8_get_codepoint(utf, n, &cp)) <= 0 ||
+ cp > 0xffff)
+ return false;
+
+ *ptr++ = L_CPU_TO_BE16(cp);
+ }
+
+ *ptr++ = 0;
+ *out_len = (uint8_t *) ptr - out_buf;
+ return true;
+}
+
+/* RFC7292 Appendix B */
+uint8_t *pkcs12_pbkdf(const char *password, const struct pkcs12_hash *hash,
+ const uint8_t *salt, size_t salt_len,
+ unsigned int iterations, uint8_t id, size_t key_len)
+{
+ /* All lengths in bytes instead of bits */
+ unsigned int passwd_len = password ? 2 * strlen(password) + 2 : 0;
+ uint8_t bmpstring[passwd_len];
+ /* Documented as v(ceiling(s/v)), usually will just equal v */
+ unsigned int s_len = (salt_len + hash->v - 1) & ~(hash->v - 1);
+ /* Documented as p(ceiling(s/p)), usually will just equal v */
+ unsigned int p_len = (passwd_len + hash->v - 1) & ~(hash->v - 1);
+ uint8_t di[hash->v + s_len + p_len];
+ uint8_t *ptr;
+ unsigned int j;
+ uint8_t *key;
+ unsigned int bytes;
+ struct l_checksum *h;
+
+ /*
+ * If non-ASCII characters are used the BMPString enoding can end
+ * up being shorter than 2 * strlen(password) + 2 but not longer
+ * after this call. Update p_len afterwards just in case.
+ * utf8_to_bmpstring doesn't need the string to be UTF-8-validated.
+ */
+ if (password && !utf8_to_bmpstring(password, bmpstring, &passwd_len))
+ return NULL;
+
+ p_len = (passwd_len + hash->v - 1) & ~(hash->v - 1);
+
+ if (!(h = l_checksum_new(hash->alg))) {
+ explicit_bzero(bmpstring, sizeof(bmpstring));
+ return NULL;
+ }
+
+ memset(di, id, hash->v);
+ ptr = di + hash->v;
+
+ for (j = salt_len; j < s_len; j += salt_len, ptr += salt_len)
+ memcpy(ptr, salt, salt_len);
+
+ if (s_len) {
+ memcpy(ptr, salt, s_len + salt_len - j);
+ ptr += s_len + salt_len - j;
+ }
+
+ for (j = passwd_len; j < p_len; j += passwd_len, ptr += passwd_len)
+ memcpy(ptr, bmpstring, passwd_len);
+
+ if (p_len)
+ memcpy(ptr, bmpstring, p_len + passwd_len - j);
+
+ explicit_bzero(bmpstring, sizeof(bmpstring));
+ key = l_malloc(key_len + hash->len);
+
+ for (bytes = 0; bytes < key_len; bytes += hash->u) {
+ uint8_t b[hash->v];
+ uint8_t *input = di;
+ unsigned int input_len = hash->v + s_len + p_len;
+
+ for (j = 0; j < iterations; j++) {
+ if (!l_checksum_update(h, input, input_len) ||
+ l_checksum_get_digest(h,
+ key + bytes,
+ hash->len) <= 0) {
+ l_checksum_free(h);
+ l_free(key);
+ return NULL;
+ }
+
+ input = key + bytes;
+ input_len = hash->u;
+ l_checksum_reset(h);
+ }
+
+ if (bytes + hash->u >= key_len)
+ break;
+
+ for (j = 0; j < hash->v - hash->u; j += hash->u)
+ memcpy(b + j, input, hash->u);
+
+ memcpy(b + j, input, hash->v - j);
+
+ ptr = di + hash->v;
+ for (j = 0; j < s_len + p_len; j += hash->v, ptr += hash->v) {
+ unsigned int k;
+ uint16_t carry = 1;
+
+ /*
+ * Not specified in the RFC7292 but implementations
+ * sum these octet strings as big-endian integers.
+ * We could use 64-bit additions here but the benefit
+ * may not compensate the cost of the byteswapping.
+ */
+ for (k = hash->v - 1; k > 0; k--) {
+ carry = ptr[k] + b[k] + carry;
+ ptr[k] = carry;
+ carry >>= 8;
+ }
+
+ ptr[k] += b[k] + carry;
+ explicit_bzero(&carry, sizeof(carry));
+ }
+
+ explicit_bzero(b, sizeof(b));
+ }
+
+ explicit_bzero(di, sizeof(di));
+ l_checksum_free(h);
+ return key;
+}
+
+/* RFC7292 Appendix A */
+static const struct pkcs12_hash pkcs12_sha1_hash = {
+ .alg = L_CHECKSUM_SHA1,
+ .len = 20,
+ .u = 20,
+ .v = 64,
+ .oid = { 5, { 0x2b, 0x0e, 0x03, 0x02, 0x1a } },
+};
+
+/* RFC8018 Section A.2 */
static struct asn1_oid pkcs5_pbkdf2_oid = {
9, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x05, 0x0c }
};
+/* RFC8018 Section A.4 */
static struct asn1_oid pkcs5_pbes2_oid = {
9, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x05, 0x0d }
};
+/* RFC8018 Section A.3 */
static const struct pkcs5_pbes1_encryption_oid {
enum l_checksum_type checksum_type;
enum l_cipher_type cipher_type;
--
2.27.0