//////////////////////////////////////////////////////////////////////////// 
// 
// Copyright (C) DSTC Pty Ltd (ACN 052 372 577) 1993, 1994, 1995.
// Unpublished work.  All Rights Reserved.
// 
// The software contained on this media is the property of the
// DSTC Pty Ltd.  Use of this software is strictly in accordance
// with the license agreement in the accompanying LICENSE.DOC 
// file. If your distribution of this software does not contain 
// a LICENSE.DOC file then you have no rights to use this 
// software in any manner and should contact DSTC at the address 
// below to determine an appropriate licensing arrangement.
// 
//      DSTC Pty Ltd
//      Level 7, GP South
//      University of Queensland
//      St Lucia, 4072
//      Australia
//      Tel: +61 7 3365 4310
//      Fax: +61 7 3365 4311
//      Email: jcsi@dstc.qut.edu.au
// 
// This software is being provided "AS IS" without warranty of
// any kind.  In no event shall DSTC Pty Ltd be liable for
// damage of any kind arising out of or in connection with
// the use or performance of this software.
// 
//////////////////////////////////////////////////////////////////////////// 

package com.dstc.security.ssl;

import java.io.IOException;
import java.security.MessageDigest;
import javax.net.ssl.*;

/**
 * An SSL v3 specific MAC class
 *
 * Will be turned into an interface with implementations for SSL v3 and
 * TLS in a subsequent version.
 *
 * @author Ming Yung
 */
public class SSLMac
{
  private MessageDigest md;
  private MessageDigest md5;
  private MessageDigest sha;
  private int padSize;

  public SSLMac(String hashAlg, int padSize)
  {
    try
    {
      this.md = MessageDigest.getInstance(hashAlg);
      this.md5 = MessageDigest.getInstance("MD5");
      this.sha = MessageDigest.getInstance("SHA");
      this.padSize = padSize;
    }
    catch (Exception e)
    {
      e.printStackTrace();
    }
  }

  public byte[] macMessage(byte[] macSecret, byte[] seqNum, 
                           byte contentType, byte[] message, 
                           byte[] version)
  {
    md.update(macSecret);
    md.update(V3Constants.PAD1, 0, padSize);
    md.update(seqNum);
    md.update(contentType);

    byte lengthOctets[] = new byte[2];
    lengthOctets[0] = (byte)((message.length >> 8) & 0xff);
    lengthOctets[1] = (byte)(message.length & 0xff);
    md.update(lengthOctets);

    md.update(message);
    byte temp[] = md.digest();
    md.update(macSecret);
    md.update(V3Constants.PAD2, 0, padSize);
    md.update(temp);
    return md.digest();
  }

  public byte[] computeMasterSecret(byte[] pre, byte[] cRandom, 
                                    byte[] sRandom)
  {
    byte[] secret = new byte[48];
    for (int i=0; i<3; i++)
    {
      System.arraycopy(doubleHash(pre, V3Constants.SALTS[i],
                                  cRandom,
                                  sRandom),
                       0, secret, 16*i, 16);
    }

    return secret;
  }

  public byte[] computeKeyBlock(byte[] master, byte[] cRandom, 
                                byte[] sRandom)
  {
    if (Debug.debug >= Debug.DEBUG_CRYPTO)
    {
      Debug.debug("masterSecret: ", master);
    }

    byte block[] = new byte[112];
    for (int i=0; i<7; i++)
    {
      System.arraycopy(doubleHash(master, V3Constants.SALTS[i],
                                  sRandom,
                                  cRandom),
                       0, block, 16*i, 16);
    }

    return block;
  }

  public byte[] computeHashes(MessageDigest shaHash, MessageDigest md5Hash,
                              byte[] master, int sender)
    throws IOException
  {
    if (sender == HandShaker.CLIENT)
    {
      return hash(shaHash, md5Hash, master, V3Constants.CLIENT, 
        V3Constants.HASH_TYPE_BOTH);
    }
    else
    {
      return hash(shaHash, md5Hash, master, V3Constants.SERVER, 
        V3Constants.HASH_TYPE_BOTH);
    }
  }

  private final byte[] doubleHash(byte[] secret, String salt,
                            byte[] firstRandom, byte[] secondRandom)
  {
    md5.update(secret);
    sha.update(salt.getBytes());
    sha.update(secret);
    sha.update(firstRandom);
    sha.update(secondRandom);
    md5.update(sha.digest());
    return md5.digest();
  }

  final byte[] hash(MessageDigest shaHash, MessageDigest md5Hash,
                            byte[] master, byte[] sender, int type)
    throws SSLException
  {
    try
    {
      sha = (MessageDigest)shaHash.clone();

      if (sender != null)
        sha.update(sender);

      sha.update(master);
      sha.update(V3Constants.PAD1, 0, 40);
      byte[] temp = sha.digest();
      sha.update(master);
      sha.update(V3Constants.PAD2, 0, 40);
      byte[] shaHashBytes = sha.digest(temp);

      if (type == V3Constants.HASH_TYPE_SHA)
        return shaHashBytes;

      md5 = (MessageDigest)md5Hash.clone();

      if (sender != null)
        md5.update(sender);

      md5.update(master);
      md5.update(V3Constants.PAD1);
      temp = md5.digest();
      md5.update(master);
      md5.update(V3Constants.PAD2);
      md5.update(temp);
      byte[] md5HashBytes = md5.digest();

      byte retval[] = new byte[36];
      System.arraycopy(md5HashBytes, 0, retval, 0, 16);
      System.arraycopy(shaHashBytes, 0, retval, 16, 20);
      return retval;
    }
    catch (CloneNotSupportedException e)
    {
      //shouldn't happen
      e.printStackTrace();
      throw new SSLException(e.getMessage());
    }
  }
}
