/* ***************************************************************** *
 * Copyright 1998 International Business Machines Corporation. All   *
 * Rights Reserved.                                                  *
 *                                                                   *
 * Please read this carefully.  Your use of this reference           *
 * implementation of certain of the IETF public-key infrastructure   *
 * specifications ("Software") indicates your acceptance of the      *
 * following.  If you do not agree to the following, do not install  *
 * or use any of the Software.                                       *
 *                                                                   *
 * Permission to use, reproduce, distribute and create derivative    *
 * works from the Software ("Software Derivative Works"), and to     *
 * distribute such Software Derivative Works is hereby granted to    *
 * you by International Business Machines Corporation ("IBM").  This *
 * permission includes a license under the patents of IBM that are   *
 * necessarily infringed by your use of the Software as provided by  *
 * IBM.                                                              *
 *                                                                   *
 * IBM licenses the Software to you on an "AS IS" basis, without     *
 * warranty of any kind.  IBM HEREBY EXPRESSLY DISCLAIMS ALL         *
 * WARRANTIES OR CONDITIONS, EITHER EXPRESS OR IMPLIED, INCLUDING,   *
 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OR CONDITIONS OF       *
 * MERCHANTABILITY, NON INFRINGEMENT AND FITNESS FOR A PARTICULAR    *
 * PURPOSE.  You are solely responsible for determining the          *
 * appropriateness of using this Software and assume all risks       *
 * associated with the use of this Software, including but not       *
 * limited to the risks of program errors, damage to or loss of      *
 * data, programs or equipment, and unavailability or interruption   *
 * of operations.                                                    *
 *                                                                   *
 * IBM WILL NOT BE LIABLE FOR ANY DIRECT DAMAGES OR FOR ANY SPECIAL, *
 * INCIDENTAL, OR  INDIRECT DAMAGES OR FOR ANY ECONOMIC              *
 * CONSEQUENTIAL DAMAGES (INCLUDING LOST PROFITS OR SAVINGS), EVEN   *
 * IF IBM HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.  IBM  *
 * will not be liable for the loss of, or damage to, your records or *
 * data, or any damages claimed by you based on a third party claim. *
 *                                                                   *
 * IBM wishes to obtain your feedback to assist in improving the     *
 * Software.  You grant IBM a world-wide, royalty-free right to use, *
 * copy, distribute, sublicense and prepare derivative works based   *
 * upon any feedback, including materials, error corrections,        *
 * Software Derivatives, enhancements, suggestions and the like that *
 * you provide to IBM relating to the Software (this does not        *
 * include products for which you charge a royalty and distribute to *
 * IBM under other terms and conditions).                            *
 *                                                                   *
 * You agree to distribute the Software and any Software Derivatives *
 * under a license agreement that: 1) is sufficient to notify all    *
 * licensees of the Software and Software Derivatives that IBM       *
 * assumes no liability for any claim that may arise regarding the   *
 * Software or Software Derivatives, and 2) that disclaims all       *
 * warranties, both express and implied, from IBM regarding the      *
 * Software and Software Derivatives.  (If you include this          *
 * Agreement with any distribution of the Software or Software       *
 * Derivatives you will have met this requirement.)  You agree that  *
 * you will not delete any copyright notices in the Software.        *
 *                                                                   *
 * This Agreement is the exclusive statement of your rights in the   *
 * Software as provided by IBM.   Except for the rights granted to   *
 * you in the second paragraph above, You are not granted any other  *
 * patent rights, including but not limited to the right to make     *
 * combinations of the Software with products that infringe IBM      *
 * patents. You agree to comply with all applicable laws and         *
 * regulations, including all export and import laws and regulation. *
 * This Agreement is governed by the laws of the State of New York.  *
 * This Agreement supersedes all other communications,               *
 * understandings or agreements we may have had prior to this        *
 * Agreement.                                                        *
 * ***************************************************************** */

#include <cssm.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <crypto.h>
#include <CdsaMem.h>
#include <jkl.h>
#include <crmsg.h>

#ifndef min
#define min(a,b)	((a) < (b) ? (a) : (b))
#endif

#ifndef max
#define max(a,b)	((a) < (b) ? (b) : (a))
#endif

#ifndef compare_oid
#define compare_oid(b, s)	memcmp(b.data, s, max(b.data_len, strlen(s)))
#endif

static  CSSM_CSP_HANDLE p12CspHandle = NULL;

static uint32
p12AttachCSP()
{
  CSSM_GUID	cspGUID;
  uint32	status;

  // We try and attach the BSAFE CSP first since that supports more
  // algorithms. But if that fails we'll fall back to cylink which only
  // supports 3-des.

  cspGUID = *JKL_Get_SwCsp_GUID();
  if ((status = JKL_AttachCSP(cspGUID, p12CspHandle)) != 0) {
    cspGUID = *JKL_Get_CylinkCsp_GUID();
    if ((status = JKL_AttachCSP(cspGUID, p12CspHandle)) != 0) {
      return status;
    }
  }
  return status;
}


static uint32
sha1(const r_buffer_t in, buffer_t &out)
{
  CSSM_ALGORITHMS       alg = CSSM_ALGID_SHA1;
  uint32                status = 0;
  CSSM_DATA             cin;
  asn_octetstring       cout;
  unsigned char         *tp;
  uint32                tl;
  
  do {
    if (p12CspHandle == NULL) {
      if ((status = p12AttachCSP())) {
	break;
      }
    }
    cin.Data = in.data;
    cin.Length = in.data_len;
    if ((status = JKL_DigestData(p12CspHandle, alg, cin, cout))) {
      break;
    }
    if ((status = cout.get_value(tp, tl))) {
      break;
    }
  } while(0);
  if (!status) {
    out.clear();
    status = out.append(tp, tl);
  }
  return status;
}

static uint32
utf82bmp(utf8String pass, buffer_t &bmp)
{
  int		x;
  uint32	status;
  do {
    bmp.clear();
    for(x = 0; x <= strlen((char *) pass); x++) {
      if ((status = bmp.append((unsigned char) 0x00))) {
	break;
      }
      if ((status = bmp.append(pass[x]))) {
	break;
      }
    }
  } while(0);
  return status;
}

uint32
CR_PKCS12_makekey(utf8String password, const buffer_t &salt, p12keytype_t type,
	       long iteration, unsigned int keysize, buffer_t &key)
{
  unsigned char	*D = NULL;
  unsigned char	*P = NULL;
  unsigned char	*S = NULL;
  unsigned char *I = NULL;
  unsigned char	*tmp = NULL;
  unsigned int	SLen = 0;
  unsigned int	PLen = 0;
  unsigned int	ILen = 0;
  unsigned int	DLen = 0;
  unsigned int	v = 64; // sha-1 block size (bytes)
  unsigned int	u = 20; // sha-1 output size  (bytes)
  unsigned int	ucnt = 0;
  buffer_t	A;
  buffer_t	hashIn;
  buffer_t	hashOut;
  buffer_t	bmpPass(ASN_SECRET);
  int		cnt = 0;
  int		rem = 0;
  int		status = 0;

  do { 
    // Buffer full of ID
    DLen = v;
    D = (unsigned char *) malloc(DLen);
    memset(D, type, DLen);

    // Buffer full of salt
    SLen = v* ((salt.data_len+v)/v);
    S = (unsigned char *) malloc(SLen);
    tmp = S;
    for(ucnt = 0; ucnt < SLen; ucnt += salt.data_len) {
      memcpy(tmp, salt.data, salt.data_len);
      tmp += salt.data_len;
    }
    if ((rem = (SLen % salt.data_len)) != 0) {
      memcpy(tmp, salt.data, rem);
    }

    // Buffer full of password
    if ((status = utf82bmp(password, bmpPass))) {
      break;
    }

    PLen = v * ((bmpPass.data_len+v)/v);
    P = (unsigned char *) malloc(PLen);
    tmp = P;
    for (ucnt = 0; ucnt < PLen; ucnt += bmpPass.data_len) {
      memcpy(tmp, bmpPass.data, bmpPass.data_len);
      tmp += bmpPass.data_len;
    }
    if ((rem = (PLen % bmpPass.data_len)) != 0) {
      memcpy(tmp, bmpPass.data, rem);
    }

    // Concat S & P
    ILen = SLen + PLen;
    if ((I = (unsigned char *) malloc(ILen)) == NULL) {
      throw "Out of memory creating PKCS#12 KEY";
    }
    tmp = I;
    memcpy(tmp, S, SLen);
    tmp += SLen;
    memcpy(tmp, P, PLen);
    hashIn.clear();
    hashIn.append(D, DLen);
    hashIn.append(I, ILen);
    A.clear();
    for(cnt = 0; cnt < iteration; cnt++) {
      if ((status = sha1(hashIn, hashOut))) {
	break;
      }
      A.append(hashIn);
      hashIn.clear();
      hashIn.append(hashOut);
    }
    if (status) break;
    key.clear();
    key.append(A.data, keysize);
  } while(0);
  if (D) {
    memset(D, 0, DLen);
    free(D);
  }
  if (S) { 
    memset(S, 0, SLen);
    free(S);
  }
  if (I) {
    memset(I, 0, ILen);
    free(I);
  }
  if (P) { 
    memset(P, 0, PLen);
    free(P);
  }
  return status;
}

enum alg_t {NO_ALG, RC2, DES3, RC4};
uint32
CR_PKCS12_DecryptContent(const buffer_t &alg, const buffer_t &salt, 
			 long iteration, utf8String password, 
			 const r_buffer_t in, buffer_t &out)
{
  buffer_t	key;
  buffer_t	iv;
  CSSM_DATA	cssmIV;
  CSSM_DATA	cssmIn;
  CSSM_DATA	cssmOut;
  CSSM_KEY	cssmKey;
  uint32	keysize = 0;
  uint32	ivsize = 0;
  alg_t		encAlg = NO_ALG;
  uint32	status = 0;
  uint32	nkeys = 1;
  
  do {
    if (!compare_oid(alg, PKCS12_PBE_SHA1_40BIT_RC2)) {
      encAlg = RC2;
      keysize = 5;
      ivsize = 8;
    } else if (!compare_oid(alg, PKCS12_PBE_SHA1_128BIT_RC2)) {
      encAlg = RC2;
      keysize = 16;
      ivsize = 8;
      nkeys = 1;
    } else if (!compare_oid(alg, PKCS12_PBE_SHA1_40BIT_RC4)) {
      encAlg = RC4;
      keysize = 5;
      ivsize = 0;
      nkeys = 1;
    } else if (!compare_oid(alg, PKCS12_PBE_SHA1_128BIT_RC4)) {
      encAlg = RC4;
      keysize = 16;
      ivsize = 0;
      nkeys = 1;
    } else if (!compare_oid(alg, PKCS12_PBE_SHA1_2KEY_3DES)) {
      encAlg = DES3;
      keysize = 8;
      ivsize = 8;
      nkeys = 2;
    } else if (!compare_oid(alg, PKCS12_PBE_SHA1_3KEY_3DES)) {
      encAlg = DES3;
      keysize = 8;
      ivsize = 8;
      nkeys = 3;
    } else {
      status = CR_PKCS12_UNKNOWN_ALG;
      break;
    }
    if ((status = CR_PKCS12_makekey(password, salt, ENCRYPTION, iteration, 
			  (keysize*nkeys), key))) {
      break;
    }
    if (ivsize) {
      if ((status = CR_PKCS12_makekey(password, salt, IV, iteration, 
				   ivsize, iv))) {
	break;
      }
    }
    if (p12CspHandle == NULL) {
      if ((status = p12AttachCSP())) {
	break;
      }
    }
    if (ivsize) {
      cssmIV.Data = iv.data;
      cssmIV.Length = iv.data_len;
    }
    cssmIn.Data = in.data;
    cssmIn.Length = in.data_len;
    // need key stuff
    switch(encAlg) {
#if 0
    case RC2:
      status = JKL_RC2_DecryptData(p12CspHandle, cssmIV, cssmKey, cssmIn, 
				   cssmOut);
      break;
    case RC4:
      status = JKL_RC4_DecryptData(p12CspHandle, cssmKey, cssmIn, cssmOut);
      break;
#endif
    case DES3:
      status = JKL_3DES_DecryptData(p12CspHandle, nkeys, cssmIV, cssmKey, 
				    cssmIn, cssmOut);
      break;
    default:
     status = CR_PKCS12_UNKNOWN_ALG;
     break;
    }
    if(status) break;
    out.clear();
    out.append(cssmOut.Data, cssmOut.Length);
  } while(0);
  return status;
}


uint32
CR_PKCS12_generate_salt(buffer_t &salt)
{
  uint32                status = 0;
  CSSM_DATA             random;

  do {
    if (p12CspHandle == NULL) {
      if ((status = p12AttachCSP())) {
	break;
      }
    }
    if ((status = JKL_GenerateRandomData(p12CspHandle, 20, random))) {
      break;
    }
  } while(0);
  if (!status) {
    salt.clear();
    salt.append(random.Data, random.Length);
  }
  return status;
}
