/*
 * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package com.sun.crypto.provider;

import java.util.Arrays;

import java.security.*;
import java.security.spec.AlgorithmParameterSpec;

import javax.crypto.*;
import javax.crypto.spec.SecretKeySpec;

import sun.security.internal.spec.TlsPrfParameterSpec;

/**
 * KeyGenerator implementation for the TLS PRF function.
 * <p>
 * This class duplicates the HMAC functionality (RFC 2104) with
 * performance optimizations (e.g. XOR'ing keys with padding doesn't
 * need to be redone for each HMAC operation).
 *
 * @author  Andreas Sterbenz
 * @since   1.6
 */
abstract class TlsPrfGenerator extends KeyGeneratorSpi {

    // magic constants and utility functions, also used by other files
    // in this package

    private final static byte[] B0 = new byte[0];

    final static byte[] LABEL_MASTER_SECRET = // "master secret"
        { 109, 97, 115, 116, 101, 114, 32, 115, 101, 99, 114, 101, 116 };

    final static byte[] LABEL_KEY_EXPANSION = // "key expansion"
        { 107, 101, 121, 32, 101, 120, 112, 97, 110, 115, 105, 111, 110 };

    final static byte[] LABEL_CLIENT_WRITE_KEY = // "client write key"
        { 99, 108, 105, 101, 110, 116, 32, 119, 114, 105, 116, 101, 32,
          107, 101, 121 };

    final static byte[] LABEL_SERVER_WRITE_KEY = // "server write key"
        { 115, 101, 114, 118, 101, 114, 32, 119, 114, 105, 116, 101, 32,
          107, 101, 121 };

    final static byte[] LABEL_IV_BLOCK = // "IV block"
        { 73, 86, 32, 98, 108, 111, 99, 107 };

    /*
     * TLS HMAC "inner" and "outer" padding.  This isn't a function
     * of the digest algorithm.
     */
    private static final byte[] HMAC_ipad64  = genPad((byte)0x36, 64);
    private static final byte[] HMAC_ipad128 = genPad((byte)0x36, 128);
    private static final byte[] HMAC_opad64  = genPad((byte)0x5c, 64);
    private static final byte[] HMAC_opad128 = genPad((byte)0x5c, 128);

    // SSL3 magic mix constants ("A", "BB", "CCC", ...)
    final static byte[][] SSL3_CONST = genConst();

    static byte[] genPad(byte b, int count) {
        byte[] padding = new byte[count];
        Arrays.fill(padding, b);
        return padding;
    }

    static byte[] concat(byte[] b1, byte[] b2) {
        int n1 = b1.length;
        int n2 = b2.length;
        byte[] b = new byte[n1 + n2];
        System.arraycopy(b1, 0, b, 0, n1);
        System.arraycopy(b2, 0, b, n1, n2);
        return b;
    }

    private static byte[][] genConst() {
        int n = 10;
        byte[][] arr = new byte[n][];
        for (int i = 0; i < n; i++) {
            byte[] b = new byte[i + 1];
            Arrays.fill(b, (byte)('A' + i));
            arr[i] = b;
        }
        return arr;
    }

    // PRF implementation

    private final static String MSG = "TlsPrfGenerator must be "
        + "initialized using a TlsPrfParameterSpec";

    private TlsPrfParameterSpec spec;

    public TlsPrfGenerator() {
    }

    protected void engineInit(SecureRandom random) {
        throw new InvalidParameterException(MSG);
    }

    protected void engineInit(AlgorithmParameterSpec params,
            SecureRandom random) throws InvalidAlgorithmParameterException {
        if (params instanceof TlsPrfParameterSpec == false) {
            throw new InvalidAlgorithmParameterException(MSG);
        }
        this.spec = (TlsPrfParameterSpec)params;
        SecretKey key = spec.getSecret();
        if ((key != null) && ("RAW".equals(key.getFormat()) == false)) {
            throw new InvalidAlgorithmParameterException(
                "Key encoding format must be RAW");
        }
    }

    protected void engineInit(int keysize, SecureRandom random) {
        throw new InvalidParameterException(MSG);
    }

    SecretKey engineGenerateKey0(boolean tls12) {
        if (spec == null) {
            throw new IllegalStateException(
                "TlsPrfGenerator must be initialized");
        }
        SecretKey key = spec.getSecret();
        byte[] secret = (key == null) ? null : key.getEncoded();
        try {
            byte[] labelBytes = spec.getLabel().getBytes("UTF8");
            int n = spec.getOutputLength();
            byte[] prfBytes = (tls12 ?
                doTLS12PRF(secret, labelBytes, spec.getSeed(), n,
                    spec.getPRFHashAlg(), spec.getPRFHashLength(),
                    spec.getPRFBlockSize()) :
                doTLS10PRF(secret, labelBytes, spec.getSeed(), n));
            return new SecretKeySpec(prfBytes, "TlsPrf");
        } catch (GeneralSecurityException e) {
            throw new ProviderException("Could not generate PRF", e);
        } catch (java.io.UnsupportedEncodingException e) {
            throw new ProviderException("Could not generate PRF", e);
        }
    }

    static byte[] doTLS12PRF(byte[] secret, byte[] labelBytes,
            byte[] seed, int outputLength,
            String prfHash, int prfHashLength, int prfBlockSize)
            throws NoSuchAlgorithmException, DigestException {
        if (prfHash == null) {
            throw new NoSuchAlgorithmException("Unspecified PRF algorithm");
        }
        MessageDigest prfMD = MessageDigest.getInstance(prfHash);
        return doTLS12PRF(secret, labelBytes, seed, outputLength,
            prfMD, prfHashLength, prfBlockSize);
    }

    static byte[] doTLS12PRF(byte[] secret, byte[] labelBytes,
            byte[] seed, int outputLength,
            MessageDigest mdPRF, int mdPRFLen, int mdPRFBlockSize)
            throws DigestException {

        if (secret == null) {
            secret = B0;
        }

        // If we have a long secret, digest it first.
        if (secret.length > mdPRFBlockSize) {
            secret = mdPRF.digest(secret);
        }

        byte[] output = new byte[outputLength];
        byte [] ipad;
        byte [] opad;

        switch (mdPRFBlockSize) {
        case 64:
            ipad = HMAC_ipad64.clone();
            opad = HMAC_opad64.clone();
            break;
        case 128:
            ipad = HMAC_ipad128.clone();
            opad = HMAC_opad128.clone();
            break;
        default:
            throw new DigestException("Unexpected block size.");
        }

        // P_HASH(Secret, label + seed)
        expand(mdPRF, mdPRFLen, secret, 0, secret.length, labelBytes,
            seed, output, ipad, opad);

        return output;
    }

    static byte[] doTLS10PRF(byte[] secret, byte[] labelBytes,
            byte[] seed, int outputLength) throws NoSuchAlgorithmException,
            DigestException {
        MessageDigest md5 = MessageDigest.getInstance("MD5");
        MessageDigest sha = MessageDigest.getInstance("SHA1");
        return doTLS10PRF(secret, labelBytes, seed, outputLength, md5, sha);
    }

    static byte[] doTLS10PRF(byte[] secret, byte[] labelBytes,
            byte[] seed, int outputLength, MessageDigest md5,
            MessageDigest sha) throws DigestException {
        /*
         * Split the secret into two halves S1 and S2 of same length.
         * S1 is taken from the first half of the secret, S2 from the
         * second half.
         * Their length is created by rounding up the length of the
         * overall secret divided by two; thus, if the original secret
         * is an odd number of bytes long, the last byte of S1 will be
         * the same as the first byte of S2.
         *
         * Note: Instead of creating S1 and S2, we determine the offset into
         * the overall secret where S2 starts.
         */

        if (secret == null) {
            secret = B0;
        }
        int off = secret.length >> 1;
        int seclen = off + (secret.length & 1);

        byte[] output = new byte[outputLength];

        // P_MD5(S1, label + seed)
        expand(md5, 16, secret, 0, seclen, labelBytes, seed, output,
            HMAC_ipad64.clone(), HMAC_opad64.clone());

        // P_SHA-1(S2, label + seed)
        expand(sha, 20, secret, off, seclen, labelBytes, seed, output,
            HMAC_ipad64.clone(), HMAC_opad64.clone());

        return output;
    }

    /*
     * @param digest the MessageDigest to produce the HMAC
     * @param hmacSize the HMAC size
     * @param secret the secret
     * @param secOff the offset into the secret
     * @param secLen the secret length
     * @param label the label
     * @param seed the seed
     * @param output the output array
     */
    private static void expand(MessageDigest digest, int hmacSize,
            byte[] secret, int secOff, int secLen, byte[] label, byte[] seed,
            byte[] output, byte[] pad1, byte[] pad2) throws DigestException {
        /*
         * modify the padding used, by XORing the key into our copy of that
         * padding.  That's to avoid doing that for each HMAC computation.
         */
        for (int i = 0; i < secLen; i++) {
            pad1[i] ^= secret[i + secOff];
            pad2[i] ^= secret[i + secOff];
        }

        byte[] tmp = new byte[hmacSize];
        byte[] aBytes = null;

        /*
         * compute:
         *
         *     P_hash(secret, seed) = HMAC_hash(secret, A(1) + seed) +
         *                            HMAC_hash(secret, A(2) + seed) +
         *                            HMAC_hash(secret, A(3) + seed) + ...
         * A() is defined as:
         *
         *     A(0) = seed
         *     A(i) = HMAC_hash(secret, A(i-1))
         */
        int remaining = output.length;
        int ofs = 0;
        while (remaining > 0) {
            /*
             * compute A() ...
             */
            // inner digest
            digest.update(pad1);
            if (aBytes == null) {
                digest.update(label);
                digest.update(seed);
            } else {
                digest.update(aBytes);
            }
            digest.digest(tmp, 0, hmacSize);

            // outer digest
            digest.update(pad2);
            digest.update(tmp);
            if (aBytes == null) {
                aBytes = new byte[hmacSize];
            }
            digest.digest(aBytes, 0, hmacSize);

            /*
             * compute HMAC_hash() ...
             */
            // inner digest
            digest.update(pad1);
            digest.update(aBytes);
            digest.update(label);
            digest.update(seed);
            digest.digest(tmp, 0, hmacSize);

            // outer digest
            digest.update(pad2);
            digest.update(tmp);
            digest.digest(tmp, 0, hmacSize);

            int k = Math.min(hmacSize, remaining);
            for (int i = 0; i < k; i++) {
                output[ofs++] ^= tmp[i];
            }
            remaining -= k;
        }
    }

    /**
     * A KeyGenerator implementation that supports TLS 1.2.
     * <p>
     * TLS 1.2 uses a different hash algorithm than 1.0/1.1 for the PRF
     * calculations.  As of 2010, there is no PKCS11-level support for TLS
     * 1.2 PRF calculations, and no known OS's have an internal variant
     * we could use.  Therefore for TLS 1.2, we are updating JSSE to request
     * a different provider algorithm:  "SunTls12Prf".  If we reused the
     * name "SunTlsPrf", the PKCS11 provider would need be updated to
     * fail correctly when presented with the wrong version number
     * (via Provider.Service.supportsParameters()), and add the
     * appropriate supportsParamters() checks into KeyGenerators (not
     * currently there).
     */
    static public class V12 extends TlsPrfGenerator {
        protected SecretKey engineGenerateKey() {
            return engineGenerateKey0(true);
        }
    }

    /**
     * A KeyGenerator implementation that supports TLS 1.0/1.1.
     */
    static public class V10 extends TlsPrfGenerator {
        protected SecretKey engineGenerateKey() {
            return engineGenerateKey0(false);
        }
    }
}
