1/*
2 *  Licensed to the Apache Software Foundation (ASF) under one or more
3 *  contributor license agreements.  See the NOTICE file distributed with
4 *  this work for additional information regarding copyright ownership.
5 *  The ASF licenses this file to You under the Apache License, Version 2.0
6 *  (the "License"); you may not use this file except in compliance with
7 *  the License.  You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *  Unless required by applicable law or agreed to in writing, software
12 *  distributed under the License is distributed on an "AS IS" BASIS,
13 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *  See the License for the specific language governing permissions and
15 *  limitations under the License.
16 */
17
18package org.apache.harmony.xnet.provider.jsse;
19
20import java.security.GeneralSecurityException;
21import java.security.MessageDigest;
22import java.security.NoSuchAlgorithmException;
23import java.util.Arrays;
24import javax.crypto.Mac;
25import javax.crypto.spec.SecretKeySpec;
26import javax.net.ssl.SSLException;
27
28/**
29 * This class provides functionality for computation
30 * of PRF values for TLS (http://www.ietf.org/rfc/rfc2246.txt)
31 * and SSL v3 (http://wp.netscape.com/eng/ssl3) protocols.
32 */
33public class PRF {
34    private static Logger.Stream logger = Logger.getStream("prf");
35
36    private static Mac md5_mac;
37    private static Mac sha_mac;
38    protected static MessageDigest md5;
39    protected static MessageDigest sha;
40    private static int md5_mac_length;
41    private static int sha_mac_length;
42
43    static private void init() {
44        try {
45            md5_mac = Mac.getInstance("HmacMD5");
46            sha_mac = Mac.getInstance("HmacSHA1");
47        } catch (NoSuchAlgorithmException e) {
48            throw new AlertException(AlertProtocol.INTERNAL_ERROR,
49                    new SSLException(
50                "There is no provider of HmacSHA1 or HmacMD5 "
51                + "algorithms installed in the system"));
52        }
53        md5_mac_length = md5_mac.getMacLength();
54        sha_mac_length = sha_mac.getMacLength();
55        try {
56            md5 = MessageDigest.getInstance("MD5");
57            sha = MessageDigest.getInstance("SHA-1");
58        } catch (Exception e) {
59            throw new AlertException(AlertProtocol.INTERNAL_ERROR,
60                    new SSLException(
61                    "Could not initialize the Digest Algorithms."));
62        }
63    }
64
65    /**
66     * Computes the value of SSLv3 pseudo random function.
67     * @param   out:    the buffer to fill up with the value of the function.
68     * @param   secret: the buffer containing the secret value to generate prf.
69     * @param   seed:   the seed to be used.
70     */
71    static synchronized void computePRF_SSLv3(byte[] out, byte[] secret, byte[] seed) {
72        if (sha == null) {
73            init();
74        }
75        int pos = 0;
76        int iteration = 1;
77        byte[] digest;
78        while (pos < out.length) {
79            byte[] pref = new byte[iteration];
80            Arrays.fill(pref, (byte) (64 + iteration++));
81            sha.update(pref);
82            sha.update(secret);
83            sha.update(seed);
84            md5.update(secret);
85            md5.update(sha.digest());
86            digest = md5.digest(); // length == 16
87            if (pos + 16 > out.length) {
88                System.arraycopy(digest, 0, out, pos, out.length - pos);
89                pos = out.length;
90            } else {
91                System.arraycopy(digest, 0, out, pos, 16);
92                pos += 16;
93            }
94        }
95    }
96
97    /**
98     * Computes the value of TLS pseudo random function.
99     * @param   out:    the buffer to fill up with the value of the function.
100     * @param   secret: the buffer containing the secret value to generate prf.
101     * @param   str_bytes:  the label bytes to be used.
102     * @param   seed:   the seed to be used.
103     */
104    synchronized static void computePRF(byte[] out, byte[] secret,
105            byte[] str_byts, byte[] seed) throws GeneralSecurityException {
106        if (sha_mac == null) {
107            init();
108        }
109        // Do concatenation of the label with the seed:
110        // (metterings show that is is faster to concatenate the arrays
111        // and to call HMAC.update on cancatenation, than twice call for
112        // each of the part, i.e.:
113        // time(HMAC.update(label+seed))
114        //          < time(HMAC.update(label)) + time(HMAC.update(seed))
115        // but it takes more memmory (approximaty on 4%)
116        /*
117        byte[] tmp_seed = new byte[seed.length + str_byts.length];
118        System.arraycopy(str_byts, 0, tmp_seed, 0, str_byts.length);
119        System.arraycopy(seed, 0, tmp_seed, str_byts.length, seed.length);
120        seed = tmp_seed;
121        */
122        SecretKeySpec keyMd5;
123        SecretKeySpec keySha1;
124        if ((secret == null) || (secret.length == 0)) {
125            secret = new byte[8];
126            keyMd5 = new SecretKeySpec(secret, "HmacMD5");
127            keySha1 = new SecretKeySpec(secret, "HmacSHA1");
128        } else {
129            int length = secret.length >> 1; // division by 2
130            int offset = secret.length & 1;  // remainder
131            keyMd5 = new SecretKeySpec(secret, 0, length + offset,
132                    "HmacMD5");
133            keySha1 = new SecretKeySpec(secret, length, length
134                    + offset, "HmacSHA1");
135        }
136
137        //byte[] str_byts = label.getBytes();
138
139        if (logger != null) {
140            logger.println("secret["+secret.length+"]: ");
141            logger.printAsHex(16, "", " ", secret);
142            logger.println("label["+str_byts.length+"]: ");
143            logger.printAsHex(16, "", " ", str_byts);
144            logger.println("seed["+seed.length+"]: ");
145            logger.printAsHex(16, "", " ", seed);
146            logger.println("MD5 key:");
147            logger.printAsHex(16, "", " ", keyMd5.getEncoded());
148            logger.println("SHA1 key:");
149            logger.printAsHex(16, "", " ", keySha1.getEncoded());
150        }
151
152        md5_mac.init(keyMd5);
153        sha_mac.init(keySha1);
154
155        int pos = 0;
156        md5_mac.update(str_byts);
157        byte[] hash = md5_mac.doFinal(seed); // A(1)
158        while (pos < out.length) {
159            md5_mac.update(hash);
160            md5_mac.update(str_byts);
161            md5_mac.update(seed);
162            if (pos + md5_mac_length < out.length) {
163                md5_mac.doFinal(out, pos);
164                pos += md5_mac_length;
165            } else {
166                System.arraycopy(md5_mac.doFinal(), 0, out,
167                        pos, out.length - pos);
168                break;
169            }
170            // make A(i)
171            hash = md5_mac.doFinal(hash);
172        }
173        if (logger != null) {
174            logger.println("P_MD5:");
175            logger.printAsHex(md5_mac_length, "", " ", out);
176        }
177
178        pos = 0;
179        sha_mac.update(str_byts);
180        hash = sha_mac.doFinal(seed); // A(1)
181        byte[] sha1hash;
182        while (pos < out.length) {
183            sha_mac.update(hash);
184            sha_mac.update(str_byts);
185            sha1hash = sha_mac.doFinal(seed);
186            for (int i = 0; (i < sha_mac_length) & (pos < out.length); i++) {
187                out[pos++] ^= sha1hash[i];
188            }
189            // make A(i)
190            hash = sha_mac.doFinal(hash);
191        }
192
193        if (logger != null) {
194            logger.println("PRF:");
195            logger.printAsHex(sha_mac_length, "", " ", out);
196        }
197    }
198}
199