/* LibTomCrypt, modular cryptographic library -- Tom St Denis */
/* SPDX-License-Identifier: Unlicense */

#include "tomcrypt_private.h"

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wconversion"
#pragma clang diagnostic ignored "-Wshorten-64-to-32"

#ifdef LTC_PKCS_12

int pkcs12_kdf(               int   hash_id,
               const unsigned char *pw,         unsigned long pwlen,
               const unsigned char *salt,       unsigned long saltlen,
                     unsigned int   iterations, unsigned char purpose,
                     unsigned char *out,        unsigned long outlen)
{
   unsigned long u = hash_descriptor[hash_id].hashsize;
   unsigned long v = hash_descriptor[hash_id].blocksize;
   unsigned long c = (outlen + u - 1) / u;
   unsigned long Slen = ((saltlen + v - 1) / v) * v;
   unsigned long Plen = ((pwlen + v - 1) / v) * v;
   unsigned long k = (Plen + Slen) / v;
   unsigned long Alen, keylen = 0;
   unsigned int tmp, i, j, n;
   unsigned char ch;
   unsigned char D[MAXBLOCKSIZE], A[MAXBLOCKSIZE], B[MAXBLOCKSIZE];
   unsigned char *I, *key;
   int err = CRYPT_ERROR;

   LTC_ARGCHK(pw   != NULL);
   LTC_ARGCHK(salt != NULL);
   LTC_ARGCHK(out  != NULL);

   key = XMALLOC(u * c);
   I   = XMALLOC(Plen + Slen);
   if (key == NULL || I == NULL) goto DONE;
   zeromem(key, u * c);

   for (i = 0; i < v;    i++) D[i] = purpose;              /* D - diversifier */
   for (i = 0; i < Slen; i++) I[i] = salt[i % saltlen];
   for (i = 0; i < Plen; i++) I[Slen + i] = pw[i % pwlen]; /* I = Salt || Pass */

   for (i = 0; i < c; i++) {
      Alen = sizeof(A);
      err = hash_memory_multi(hash_id, A, &Alen, D, v, I, Slen + Plen, LTC_NULL); /* A = HASH(D || I) */
      if (err != CRYPT_OK) goto DONE;
      for (j = 1; j < iterations; j++) {
         err = hash_memory(hash_id, A, Alen, A, &Alen); /* A = HASH(A) */
         if (err != CRYPT_OK) goto DONE;
      }
      /* fill buffer B with A */
      for (j = 0; j < v; j++) B[j] = A[j % Alen];
      /* B += 1 */
      for (j = v; j > 0; j--) {
         if (++B[j - 1] != 0) break;
      }
      /* I_n += B */
      for (n = 0; n < k; n++) {
         ch = 0;
         for (j = v; j > 0; j--) {
            tmp = I[n * v + j - 1] + B[j - 1] + ch;
            ch = (unsigned char)((tmp >> 8) & 0xFF);
            I[n * v + j - 1] = (unsigned char)(tmp & 0xFF);
         }
      }
      /* store derived key block */
      XMEMCPY(&key[keylen], A, Alen);
      keylen += Alen;
   }

   XMEMCPY(out, key, outlen);
   err = CRYPT_OK;
DONE:
   if (I) {
      zeromem(I, Plen + Slen);
      XFREE(I);
   }
   if (key) {
      zeromem(key, u * c);
      XFREE(key);
   }
   return err;
}

#endif

#pragma clang diagnostic pop
