blob: 00705cc39f2b3b5817efb05fa1b160d9d3dcc4e0 [file] [log] [blame]
/*
* 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.security.MessageDigest;
import java.util.Arrays;
import javax.crypto.Cipher;
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 SSL v3
* (http://wp.netscape.com/eng/ssl3) Record Protocol and provides
* relating encryption/decryption functionality.
* The work functionality is based on the security
* parameters negotiated during the handshake.
*/
public class ConnectionStateSSLv3 extends ConnectionState {
// digest to create and check the message integrity info
private final MessageDigest messageDigest;
private final byte[] mac_write_secret;
private final byte[] mac_read_secret;
// paddings
private final byte[] pad_1;
private final byte[] pad_2;
// array will hold the part of the MAC material:
// length of 3 == 1(SSLCompressed.type) + 2(SSLCompressed.length)
// (more on SSLv3 MAC computation and payload protection see
// SSL v3 specification, p. 5.2.3)
private final byte[] mac_material_part = new byte[3];
/**
* Creates the instance of SSL v3 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 SSL v3 Protocol specification.
* (http://www.mozilla.org/projects/security/pki/nss/ssl/draft302.txt)
*/
protected ConnectionStateSSLv3(SSLSessionImpl session) {
try {
CipherSuite cipherSuite = session.cipherSuite;
boolean is_exportabe = cipherSuite.isExportable();
hash_size = cipherSuite.getMACLength();
int key_size = (is_exportabe)
? cipherSuite.keyMaterial
: cipherSuite.expandedKeyMaterial;
int iv_size = cipherSuite.ivSize;
block_size = cipherSuite.getBlockSize();
String algName = cipherSuite.getBulkEncryptionAlgorithm();
String hashName = cipherSuite.getHashName();
if (logger != null) {
logger.println("ConnectionStateSSLv3.create:");
logger.println(" cipher suite name: "
+ session.getCipherSuite());
logger.println(" encryption alg name: " + algName);
logger.println(" hash alg name: " + hashName);
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_SSLv3(key_block, session.master_secret, 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) {
if (logger != null) {
logger.println("ConnectionStateSSLv3: is_exportable");
}
MessageDigest md5 = MessageDigest.getInstance("MD5");
md5.update(client_key);
md5.update(clientRandom);
md5.update(serverRandom);
client_key = md5.digest();
md5.update(server_key);
md5.update(serverRandom);
md5.update(clientRandom);
server_key = md5.digest();
key_size = cipherSuite.expandedKeyMaterial;
if (block_size != 0) {
md5.update(clientRandom);
md5.update(serverRandom);
clientIV = new IvParameterSpec(md5.digest(), 0, iv_size);
md5.update(serverRandom);
md5.update(clientRandom);
serverIV = new IvParameterSpec(md5.digest(), 0, iv_size);
}
} else if (block_size != 0) {
clientIV = new IvParameterSpec(key_block,
2*hash_size+2*key_size, iv_size);
serverIV = new IvParameterSpec(key_block,
2*hash_size+2*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, 0, key_size);
logger.println("server_key");
logger.print(server_key, 0, key_size);
if (clientIV != null) {
logger.println("client_iv");
logger.print(clientIV.getIV());
logger.println("server_iv");
logger.print(serverIV.getIV());
} else {
logger.println("no IV.");
}
}
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, 0, key_size, algName),
clientIV);
decCipher.init(Cipher.DECRYPT_MODE,
new SecretKeySpec(server_key, 0, key_size, algName),
serverIV);
} else { // server side
encCipher.init(Cipher.ENCRYPT_MODE,
new SecretKeySpec(server_key, 0, key_size, algName),
serverIV);
decCipher.init(Cipher.DECRYPT_MODE,
new SecretKeySpec(client_key, 0, key_size, algName),
clientIV);
}
}
messageDigest = MessageDigest.getInstance(hashName);
if (is_client) { // client side
mac_write_secret = client_mac_secret;
mac_read_secret = server_mac_secret;
} else { // server side
mac_write_secret = server_mac_secret;
mac_read_secret = client_mac_secret;
}
if (hashName.equals("MD5")) {
pad_1 = SSLv3Constants.MD5pad1;
pad_2 = SSLv3Constants.MD5pad2;
} else {
pad_1 = SSLv3Constants.SHApad1;
pad_2 = SSLv3Constants.SHApad2;
}
} 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_part[0] = type;
mac_material_part[1] = (byte) ((0x00FF00 & len) >> 8);
mac_material_part[2] = (byte) (0x0000FF & len);
messageDigest.update(mac_write_secret);
messageDigest.update(pad_1);
messageDigest.update(write_seq_num);
messageDigest.update(mac_material_part);
messageDigest.update(fragment, offset, len);
byte[] digest = messageDigest.digest();
messageDigest.update(mac_write_secret);
messageDigest.update(pad_2);
messageDigest.update(digest);
digest = messageDigest.digest();
System.arraycopy(digest, 0, res, len, hash_size);
//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.encrypt: "
+ (block_size != 0
? "GenericBlockCipher with padding["
+padding_length+"]:"
: "GenericStreamCipher:"));
logger.print(res);
}
byte[] rez = new byte[encCipher.getOutputSize(res.length)];
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.
* @throws AlertException if alert was occured.
*/
@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];
}
byte[] mac_value;
mac_material_part[0] = type;
mac_material_part[1] = (byte) ((0x00FF00 & content.length) >> 8);
mac_material_part[2] = (byte) (0x0000FF & content.length);
messageDigest.update(mac_read_secret);
messageDigest.update(pad_1);
messageDigest.update(read_seq_num);
messageDigest.update(mac_material_part);
messageDigest.update(data, 0, content.length);
mac_value = messageDigest.digest();
messageDigest.update(mac_read_secret);
messageDigest.update(pad_2);
messageDigest.update(mac_value);
mac_value = messageDigest.digest();
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;
}
/**
* Shutdown the protocol. It will be impossible to use the instance
* after the calling of this method.
*/
@Override
protected void shutdown() {
Arrays.fill(mac_write_secret, (byte) 0);
Arrays.fill(mac_read_secret, (byte) 0);
super.shutdown();
}
}