text: /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
  /* lib/crypto/builtin/pbkdf2.c - Implementation of PBKDF2 from RFC 2898 */
  /*
   * Copyright 2002, 2008 by the Massachusetts Institute of Technology.
   * All Rights Reserved.
   *
   * Export of this software from the United States of America may
   *   require a specific license from the United States Government.
   *   It is the responsibility of any person or organization contemplating
   *   export to obtain such a license before exporting.
   *
   * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
   * distribute this software and its documentation for any purpose and
   * without fee is hereby granted, provided that the above copyright
   * notice appear in all copies and that both that copyright notice and
   * this permission notice appear in supporting documentation, and that
   * the name of M.I.T. not be used in advertising or publicity pertaining
   * to distribution of the software without specific, written prior
   * permission.  Furthermore if you modify this software you must label
   * your software as modified software and not distribute it in such a
   * fashion that it might be confused with the original M.I.T. software.
   * M.I.T. makes no representations about the suitability of
   * this software for any purpose.  It is provided "as is" without express
   * or implied warranty.
   */
  
  #include <ctype.h>
  #include "crypto_int.h"
  
  /*
   * RFC 2898 specifies PBKDF2 in terms of an underlying pseudo-random
   * function with two arguments (password and salt||blockindex).  Right
   * now we only use PBKDF2 with the hmac-sha1 PRF, also specified in
   * RFC 2898, which invokes HMAC with the password as the key and the
   * second argument as the text.  (HMAC accepts any key size up to the
   * block size; the password is pre-hashed with unkeyed SHA1 if it is
   * longer than the block size.)
   *
   * For efficiency, it is better to generate the key from the password
   * once at the beginning, so we specify prf_fn in terms of a
   * krb5_key first argument.  That might not be convenient for a PRF
   * which uses the password in some other way, so this might need to be
   * adjusted in the future.
   */
  
  typedef krb5_error_code (*prf_fn)(krb5_key pass, krb5_data *salt,
                                    krb5_data *out);
  
  static int debug_hmac = 0;
  
  static void printd (const char *descr, krb5_data *d) {
      unsigned int i, j;
      const int r = 16;
  
      printf("%s:", descr);
  
      for (i = 0; i < d->length; i += r) {
          printf("\n  %04x: ", i);
          for (j = i; j < i + r && j < d->length; j++)
              printf(" %02x", 0xff & d->data[j]);
          for (; j < i + r; j++)
              printf("   ");
          printf("   ");
          for (j = i; j < i + r && j < d->length; j++) {
              int c = 0xff & d->data[j];
              printf("%c", isprint(c) ? c : '.');
          }
      }
      printf("\n");
  }
  
  /*
   * Implements the hmac-sha1 PRF.  pass has been pre-hashed (if
   * necessary) and converted to a key already; salt has had the block
   * index appended to the original salt.
   *
   * NetBSD 8 declares an hmac() function in stdlib.h, so avoid that name.
   */
  static krb5_error_code
  k5_hmac(const struct krb5_hash_provider *hash, krb5_keyblock *pass,
          krb5_data *salt, krb5_data *out)
  {
      krb5_error_code err;
      krb5_crypto_iov iov;
  
      if (debug_hmac)
          printd(" hmac input", salt);
      iov.flags = KRB5_CRYPTO_TYPE_DATA;
      iov.data = *salt;
      err = krb5int_hmac_keyblock(hash, pass, &iov, 1, out);
      if (err == 0 && debug_hmac)
          printd(" hmac output", out);
      return err;
  }
  
  static krb5_error_code
  F(char *output, char *u_tmp1, char *u_tmp2,
    const struct krb5_hash_provider *hash, size_t hlen, krb5_keyblock *pass,
    const krb5_data *salt, unsigned long count, int i)
  {
      unsigned char ibytes[4];
      unsigned int j, k;
      krb5_data sdata;
      krb5_data out;
      krb5_error_code err;
  
      /* Compute U_1.  */
      store_32_be(i, ibytes);
  
      memcpy(u_tmp2, salt->data, salt->length);
      memcpy(u_tmp2 + salt->length, ibytes, 4);
      sdata = make_data(u_tmp2, salt->length + 4);
  
      out = make_data(u_tmp1, hlen);
  
      err = k5_hmac(hash, pass, &sdata, &out);
      if (err)
          return err;
  
      memcpy(output, u_tmp1, hlen);
  
      /* Compute U_2, .. U_c.  */
      sdata.length = hlen;
      for (j = 2; j <= count; j++) {
          memcpy(u_tmp2, u_tmp1, hlen);
          err = k5_hmac(hash, pass, &sdata, &out);
          if (err)
              return err;
  
          /* And xor them together.  */
          for (k = 0; k < hlen; k++)
              output[k] ^= u_tmp1[k];
      }
      return 0;
  }
  
  static krb5_error_code
  pbkdf2(const struct krb5_hash_provider *hash, krb5_keyblock *pass,
         const krb5_data *salt, unsigned long count, const krb5_data *output)
  {
      size_t hlen = hash->hashsize;
      int l, i;
      char *utmp1, *utmp2;
      char utmp3[128];             /* XXX length shouldn't be hardcoded! */
  
      if (output->length == 0 || hlen == 0)
          abort();
      /* Step 1 & 2.  */
      if (output->length / hlen > 0xffffffff)
          abort();
      /* Step 2.  */
      l = (output->length + hlen - 1) / hlen;
  
      utmp1 = /*output + dklen; */ malloc(hlen);
      if (utmp1 == NULL)
          return ENOMEM;
      utmp2 = /*utmp1 + hlen; */ malloc(salt->length + 4 + hlen);
      if (utmp2 == NULL) {
          free(utmp1);
          return ENOMEM;
      }
  
      /* Step 3.  */
      for (i = 1; i <= l; i++) {
          krb5_error_code err;
          char *out;
  
          if (i == l)
              out = utmp3;
          else
              out = output->data + (i-1) * hlen;
          err = F(out, utmp1, utmp2, hash, hlen, pass, salt, count, i);
          if (err) {
              free(utmp1);
              free(utmp2);
              return err;
          }
          if (i == l)
              memcpy(output->data + (i-1) * hlen, utmp3,
                     output->length - (i-1) * hlen);
  
      }
      free(utmp1);
      free(utmp2);
      return 0;
  }
  
  krb5_error_code
  krb5int_pbkdf2_hmac(const struct krb5_hash_provider *hash,
                      const krb5_data *out, unsigned long count,
                      const krb5_data *pass, const krb5_data *salt)
  {
      krb5_keyblock keyblock;
      char tmp[128];
      krb5_data d;
      krb5_crypto_iov iov;
      krb5_error_code err;
  
      assert(hash->hashsize <= sizeof(tmp));
      if (pass->length > hash->blocksize) {
          d = make_data(tmp, hash->hashsize);
          iov.flags = KRB5_CRYPTO_TYPE_DATA;
          iov.data = *pass;
          err = hash->hash(&iov, 1, &d);
          if (err)
              return err;
          keyblock.length = d.length;
          keyblock.contents = (krb5_octet *) d.data;
      } else {
          keyblock.length = pass->length;
          keyblock.contents = (krb5_octet *) pass->data;
      }
      keyblock.enctype = ENCTYPE_NULL;
  
      err = pbkdf2(hash, &keyblock, salt, count, out);
      return err;
  }
  