/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.harmony.xnet.provider.jsse; import java.security.GeneralSecurityException; import java.util.Arrays; import javax.crypto.Cipher; import javax.crypto.Mac; import javax.crypto.NullCipher; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import javax.net.ssl.SSLProtocolException; /** * This class encapsulates the operating environment of the TLS v1 * (http://www.ietf.org/rfc/rfc2246.txt) Record Protocol and provides * relating encryption/decryption functionality. * The work functionality is based on the security * parameters negotiated during the handshake. */ public class ConnectionStateTLS extends ConnectionState { // Pre-calculated prf label values: // "key expansion".getBytes() private static byte[] KEY_EXPANSION_LABEL = { (byte) 0x6B, (byte) 0x65, (byte) 0x79, (byte) 0x20, (byte) 0x65, (byte) 0x78, (byte) 0x70, (byte) 0x61, (byte) 0x6E, (byte) 0x73, (byte) 0x69, (byte) 0x6F, (byte) 0x6E }; // "client write key".getBytes() private static byte[] CLIENT_WRITE_KEY_LABEL = { (byte) 0x63, (byte) 0x6C, (byte) 0x69, (byte) 0x65, (byte) 0x6E, (byte) 0x74, (byte) 0x20, (byte) 0x77, (byte) 0x72, (byte) 0x69, (byte) 0x74, (byte) 0x65, (byte) 0x20, (byte) 0x6B, (byte) 0x65, (byte) 0x79 }; // "server write key".getBytes() private static byte[] SERVER_WRITE_KEY_LABEL = { (byte) 0x73, (byte) 0x65, (byte) 0x72, (byte) 0x76, (byte) 0x65, (byte) 0x72, (byte) 0x20, (byte) 0x77, (byte) 0x72, (byte) 0x69, (byte) 0x74, (byte) 0x65, (byte) 0x20, (byte) 0x6B, (byte) 0x65, (byte) 0x79 }; // "IV block".getBytes() private static byte[] IV_BLOCK_LABEL = { (byte) 0x49, (byte) 0x56, (byte) 0x20, (byte) 0x62, (byte) 0x6C, (byte) 0x6F, (byte) 0x63, (byte) 0x6B }; // MACs to create and check the message integrity info private final Mac encMac; private final Mac decMac; // Once created permanently used array: // is used to create the header of the MAC material value: // 5 == 1(TLSCompressed.type) + 2(TLSCompressed.version) + // 2(TLSCompressed.length) private final byte[] mac_material_header = new byte[] {0, 3, 1, 0, 0}; /** * Creates the instance of TLS v1 Connection State. All of the * security parameters are provided by session object. * @param session: the sessin object which incapsulates * all of the security parameters established by handshake protocol. * The key calculation for the state is done according * to the TLS v 1.0 Protocol specification. * (http://www.ietf.org/rfc/rfc2246.txt) */ protected ConnectionStateTLS(SSLSessionImpl session) { try { CipherSuite cipherSuite = session.cipherSuite; hash_size = cipherSuite.getMACLength(); boolean is_exportabe = cipherSuite.isExportable(); int key_size = (is_exportabe) ? cipherSuite.keyMaterial : cipherSuite.expandedKeyMaterial; int iv_size = cipherSuite.ivSize; block_size = cipherSuite.getBlockSize(); String algName = cipherSuite.getBulkEncryptionAlgorithm(); String macName = cipherSuite.getHmacName(); if (logger != null) { logger.println("ConnectionStateTLS.create:"); logger.println(" cipher suite name: " + cipherSuite.getName()); logger.println(" encryption alg name: " + algName); logger.println(" mac alg name: " + macName); logger.println(" hash size: " + hash_size); logger.println(" block size: " + block_size); logger.println(" IV size:" + iv_size); logger.println(" key size: " + key_size); } byte[] clientRandom = session.clientRandom; byte[] serverRandom = session.serverRandom; // so we need PRF value of size of // 2*hash_size + 2*key_size + 2*iv_size byte[] key_block = new byte[2*hash_size + 2*key_size + 2*iv_size]; byte[] seed = new byte[clientRandom.length + serverRandom.length]; System.arraycopy(serverRandom, 0, seed, 0, serverRandom.length); System.arraycopy(clientRandom, 0, seed, serverRandom.length, clientRandom.length); PRF.computePRF(key_block, session.master_secret, KEY_EXPANSION_LABEL, seed); byte[] client_mac_secret = new byte[hash_size]; byte[] server_mac_secret = new byte[hash_size]; byte[] client_key = new byte[key_size]; byte[] server_key = new byte[key_size]; boolean is_client = !session.isServer; System.arraycopy(key_block, 0, client_mac_secret, 0, hash_size); System.arraycopy(key_block, hash_size, server_mac_secret, 0, hash_size); System.arraycopy(key_block, 2*hash_size, client_key, 0, key_size); System.arraycopy(key_block, 2*hash_size+key_size, server_key, 0, key_size); IvParameterSpec clientIV = null; IvParameterSpec serverIV = null; if (is_exportabe) { System.arraycopy(clientRandom, 0, seed, 0, clientRandom.length); System.arraycopy(serverRandom, 0, seed, clientRandom.length, serverRandom.length); byte[] final_client_key = new byte[cipherSuite.expandedKeyMaterial]; byte[] final_server_key = new byte[cipherSuite.expandedKeyMaterial]; PRF.computePRF(final_client_key, client_key, CLIENT_WRITE_KEY_LABEL, seed); PRF.computePRF(final_server_key, server_key, SERVER_WRITE_KEY_LABEL, seed); client_key = final_client_key; server_key = final_server_key; if (block_size != 0) { byte[] iv_block = new byte[2*iv_size]; PRF.computePRF(iv_block, null, IV_BLOCK_LABEL, seed); clientIV = new IvParameterSpec(iv_block, 0, iv_size); serverIV = new IvParameterSpec(iv_block, iv_size, iv_size); } } else if (block_size != 0) { clientIV = new IvParameterSpec(key_block, 2*(hash_size+key_size), iv_size); serverIV = new IvParameterSpec(key_block, 2*(hash_size+key_size)+iv_size, iv_size); } if (logger != null) { logger.println("is exportable: "+is_exportabe); logger.println("master_secret"); logger.print(session.master_secret); logger.println("client_random"); logger.print(clientRandom); logger.println("server_random"); logger.print(serverRandom); //logger.println("key_block"); //logger.print(key_block); logger.println("client_mac_secret"); logger.print(client_mac_secret); logger.println("server_mac_secret"); logger.print(server_mac_secret); logger.println("client_key"); logger.print(client_key); logger.println("server_key"); logger.print(server_key); if (clientIV == null) { logger.println("no IV."); } else { logger.println("client_iv"); logger.print(clientIV.getIV()); logger.println("server_iv"); logger.print(serverIV.getIV()); } } if (algName == null) { encCipher = new NullCipher(); decCipher = new NullCipher(); } else { encCipher = Cipher.getInstance(algName); decCipher = Cipher.getInstance(algName); if (is_client) { // client side encCipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(client_key, algName), clientIV); decCipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(server_key, algName), serverIV); } else { // server side encCipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(server_key, algName), serverIV); decCipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(client_key, algName), clientIV); } } encMac = Mac.getInstance(macName); decMac = Mac.getInstance(macName); if (is_client) { // client side encMac.init(new SecretKeySpec(client_mac_secret, macName)); decMac.init(new SecretKeySpec(server_mac_secret, macName)); } else { // server side encMac.init(new SecretKeySpec(server_mac_secret, macName)); decMac.init(new SecretKeySpec(client_mac_secret, macName)); } } catch (Exception e) { e.printStackTrace(); throw new AlertException(AlertProtocol.INTERNAL_ERROR, new SSLProtocolException( "Error during computation of security parameters")); } } /** * Creates the GenericStreamCipher or GenericBlockCipher * data structure for specified data of specified type. * @throws AlertException if alert was occurred. */ @Override protected byte[] encrypt(byte type, byte[] fragment, int offset, int len) { try { int content_mac_length = len + hash_size; int padding_length = (block_size == 0) ? 0 : getPaddingSize(++content_mac_length); byte[] res = new byte[content_mac_length + padding_length]; System.arraycopy(fragment, offset, res, 0, len); mac_material_header[0] = type; mac_material_header[3] = (byte) ((0x00FF00 & len) >> 8); mac_material_header[4] = (byte) (0x0000FF & len); encMac.update(write_seq_num); encMac.update(mac_material_header); encMac.update(fragment, offset, len); encMac.doFinal(res, len); //if (logger != null) { // logger.println("MAC Material:"); // logger.print(write_seq_num); // logger.print(mac_material_header); // logger.print(fragment, offset, len); //} if (block_size != 0) { // do padding: Arrays.fill(res, content_mac_length-1, res.length, (byte) (padding_length)); } if (logger != null) { logger.println("SSLRecordProtocol.do_encryption: Generic" + (block_size != 0 ? "BlockCipher with padding["+padding_length+"]:" : "StreamCipher:")); logger.print(res); } byte[] rez = new byte[encCipher.getOutputSize(res.length)]; // We should not call just doFinal because it reinitialize // the cipher, but as says rfc 2246: // "For stream ciphers that do not use a synchronization // vector (such as RC4), the stream cipher state from the end // of one record is simply used on the subsequent packet." // and for block ciphers: // "The IV for subsequent records is the last ciphertext block from // the previous record." // i.e. we should keep the cipher state. encCipher.update(res, 0, res.length, rez); incSequenceNumber(write_seq_num); return rez; } catch (GeneralSecurityException e) { e.printStackTrace(); throw new AlertException(AlertProtocol.INTERNAL_ERROR, new SSLProtocolException("Error during the encryption")); } } /** * Retrieves the fragment of the Plaintext structure of * the specified type from the provided data representing * the Generic[Stream|Block]Cipher structure. * @throws AlertException if alert was occurred. */ @Override protected byte[] decrypt(byte type, byte[] fragment, int offset, int len) { // plain data of the Generic[Stream|Block]Cipher structure byte[] data = decCipher.update(fragment, offset, len); // the 'content' part of the structure byte[] content; if (block_size != 0) { // check padding int padding_length = data[data.length-1]; for (int i=0; i<padding_length; i++) { if (data[data.length-2-i] != padding_length) { throw new AlertException( AlertProtocol.DECRYPTION_FAILED, new SSLProtocolException( "Received message has bad padding")); } } content = new byte[data.length - hash_size - padding_length - 1]; } else { content = new byte[data.length - hash_size]; } mac_material_header[0] = type; mac_material_header[3] = (byte) ((0x00FF00 & content.length) >> 8); mac_material_header[4] = (byte) (0x0000FF & content.length); decMac.update(read_seq_num); decMac.update(mac_material_header); decMac.update(data, 0, content.length); // mac.update(fragment); byte[] mac_value = decMac.doFinal(); if (logger != null) { logger.println("Decrypted:"); logger.print(data); //logger.println("MAC Material:"); //logger.print(read_seq_num); //logger.print(mac_material_header); //logger.print(data, 0, content.length); logger.println("Expected mac value:"); logger.print(mac_value); } // checking the mac value for (int i=0; i<hash_size; i++) { if (mac_value[i] != data[i+content.length]) { throw new AlertException(AlertProtocol.BAD_RECORD_MAC, new SSLProtocolException("Bad record MAC")); } } System.arraycopy(data, 0, content, 0, content.length); incSequenceNumber(read_seq_num); return content; } }