Return-Path: Delivered-To: apmail-incubator-harmony-commits-archive@www.apache.org Received: (qmail 79835 invoked from network); 18 Jul 2006 22:50:53 -0000 Received: from hermes.apache.org (HELO mail.apache.org) (209.237.227.199) by minotaur.apache.org with SMTP; 18 Jul 2006 22:50:53 -0000 Received: (qmail 86073 invoked by uid 500); 18 Jul 2006 22:50:52 -0000 Delivered-To: apmail-incubator-harmony-commits-archive@incubator.apache.org Received: (qmail 85813 invoked by uid 500); 18 Jul 2006 22:50:50 -0000 Mailing-List: contact harmony-commits-help@incubator.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: harmony-dev@incubator.apache.org Delivered-To: mailing list harmony-commits@incubator.apache.org Received: (qmail 85673 invoked by uid 99); 18 Jul 2006 22:50:49 -0000 Received: from asf.osuosl.org (HELO asf.osuosl.org) (140.211.166.49) by apache.org (qpsmtpd/0.29) with ESMTP; Tue, 18 Jul 2006 15:50:49 -0700 X-ASF-Spam-Status: No, hits=-9.4 required=10.0 tests=ALL_TRUSTED,NO_REAL_NAME X-Spam-Check-By: apache.org Received-SPF: pass (asf.osuosl.org: local policy) Received: from [140.211.166.113] (HELO eris.apache.org) (140.211.166.113) by apache.org (qpsmtpd/0.29) with ESMTP; Tue, 18 Jul 2006 15:50:44 -0700 Received: by eris.apache.org (Postfix, from userid 65534) id F08C21A981D; Tue, 18 Jul 2006 15:50:23 -0700 (PDT) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r423275 [2/7] - in /incubator/harmony/enhanced/classlib/trunk/modules/x-net/src/main/java/org: ./ apache/ apache/harmony/ apache/harmony/security/ apache/harmony/security/provider/ apache/harmony/security/provider/jsse/ Date: Tue, 18 Jul 2006 22:50:14 -0000 To: harmony-commits@incubator.apache.org From: geirm@apache.org X-Mailer: svnmailer-1.0.8 Message-Id: <20060718225023.F08C21A981D@eris.apache.org> X-Virus-Checked: Checked by ClamAV on apache.org X-Spam-Rating: minotaur.apache.org 1.6.2 0/1000/N Added: incubator/harmony/enhanced/classlib/trunk/modules/x-net/src/main/java/org/apache/harmony/security/provider/jsse/ClientHandshakeImpl.java URL: http://svn.apache.org/viewvc/incubator/harmony/enhanced/classlib/trunk/modules/x-net/src/main/java/org/apache/harmony/security/provider/jsse/ClientHandshakeImpl.java?rev=423275&view=auto ============================================================================== --- incubator/harmony/enhanced/classlib/trunk/modules/x-net/src/main/java/org/apache/harmony/security/provider/jsse/ClientHandshakeImpl.java (added) +++ incubator/harmony/enhanced/classlib/trunk/modules/x-net/src/main/java/org/apache/harmony/security/provider/jsse/ClientHandshakeImpl.java Tue Jul 18 15:50:12 2006 @@ -0,0 +1,620 @@ +/* + * Copyright 2006 The Apache Software Foundation or its licensors, as applicable. + * + * Licensed 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. + */ + +/** + * @author Boris Kuznetsov + * @version $Revision$ + */ + +package org.apache.harmony.security.provider.jsse; + +import java.io.IOException; +import java.security.AccessController; +import java.security.Key; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.PrivilegedExceptionAction; +import java.security.PublicKey; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.Arrays; +import java.util.Enumeration; + +import javax.crypto.Cipher; +import javax.crypto.KeyAgreement; +import javax.crypto.interfaces.DHKey; +import javax.crypto.interfaces.DHPublicKey; +import javax.crypto.spec.DHParameterSpec; +import javax.crypto.spec.DHPublicKeySpec; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSessionContext; +import javax.net.ssl.X509ExtendedKeyManager; + +/** + * Client side handshake protocol implementation. + * Handshake protocol operates on top of the Record Protocol. + * It is responsible for session negotiating. + * + * The implementation proceses inbound server handshake messages, + * creates and sends respond messages. Outbound messages are supplied + * to Record Protocol. Detected errors are reported to the Alert protocol. + * + * @see TLS 1.0 spec., 7. The TLS Handshake Protocol + * (http://www.ietf.org/rfc/rfc2246.txt) + * + */ +public class ClientHandshakeImpl extends HandshakeProtocol { + + /** + * Creates Client Handshake Implementation + * + * @param owner + */ + ClientHandshakeImpl(Object owner) { + super(owner); + } + + /** + * Starts handshake + * + */ + public void start() { + if (session == null) { // initial handshake + session = findSessionToResume(); + } else { // start session renegotiation + if (clientHello != null && this.status != FINISHED) { + // current negotiation has not completed + return; // ignore + } + if (!session.isValid()) { + session = null; + } + } + if (session != null) { + isResuming = true; + } else if (parameters.getEnableSessionCreation()){ + isResuming = false; + session = new SSLSessionImpl(parameters.getSecureRandom()); + session.protocol = ProtocolVersion.getLatestVersion(parameters + .getEnabledProtocols()); + recordProtocol.setVersion(session.protocol.version); + } else { + fatalAlert(AlertProtocol.HANDSHAKE_FAILURE, "SSL Session may not be created "); + } + startSession(); + } + + /** + * Starts renegotiation on a new session + * + */ + private void renegotiateNewSession() { + if (parameters.getEnableSessionCreation()){ + isResuming = false; + session = new SSLSessionImpl(parameters.getSecureRandom()); + session.protocol = ProtocolVersion.getLatestVersion(parameters + .getEnabledProtocols()); + recordProtocol.setVersion(session.protocol.version); + startSession(); + } else { + status = NOT_HANDSHAKING; + sendWarningAlert(AlertProtocol.NO_RENEGOTIATION); + } + } + + /* + * Starts/resumes session + */ + private void startSession() { + CipherSuite[] cipher_suites; + if (isResuming) { + cipher_suites = new CipherSuite[] { session.cipherSuite }; + } else { + cipher_suites = parameters.enabledCipherSuites; + } + clientHello = new ClientHello(parameters.getSecureRandom(), + session.protocol.version, session.id, cipher_suites); + session.clientRandom = clientHello.random; + send(clientHello); + status = NEED_UNWRAP; + } + + /** + * Proceses inbound handshake messages + * @param bytes + */ + public void unwrap(byte[] bytes) { + if (this.delegatedTaskErr != null) { + Exception e = this.delegatedTaskErr; + this.delegatedTaskErr = null; + this.fatalAlert(AlertProtocol.HANDSHAKE_FAILURE, "Error in delegated task", e); + } + int handshakeType; + io_stream.append(bytes); + while (io_stream.available() > 0) { + io_stream.mark(); + int length; + try { + handshakeType = io_stream.read(); + length = io_stream.readUint24(); + if (io_stream.available() < length) { + io_stream.reset(); + return; + } + switch (handshakeType) { + case 0: // HELLO_REQUEST + // we don't need to take this message into account + // during FINISH message verification, so remove it + io_stream.removeFromMarkedPosition(); + if (clientHello != null + && (clientFinished == null || serverFinished == null)) { + //currently negotiating - ignore + break; + } + // renegotiate + if (session.isValid()) { + session = (SSLSessionImpl) session.clone(); + isResuming = true; + startSession(); + } else { + // if SSLSession is invalidated (e.g. timeout limit is + // exceeded) connection can't resume the session. + renegotiateNewSession(); + } + break; + case 2: // SERVER_HELLO + if (clientHello == null || serverHello != null) { + unexpectedMessage(); + return; + } + serverHello = new ServerHello(io_stream, length); + + //check protocol version + ProtocolVersion servProt = ProtocolVersion + .getByVersion(serverHello.server_version); + String[] enabled = parameters.getEnabledProtocols(); + find: { + for (int i = 0; i < enabled.length; i++) { + if (servProt.equals(ProtocolVersion + .getByName(enabled[i]))) { + break find; + } + } + fatalAlert(AlertProtocol.HANDSHAKE_FAILURE, + "Bad server hello protocol version"); + } + + // check compression method + if (serverHello.compression_method != 0) { + fatalAlert(AlertProtocol.HANDSHAKE_FAILURE, + "Bad server hello compression method"); + } + + //check cipher_suite + CipherSuite[] enabledSuites = parameters.enabledCipherSuites; + find: { + for (int i = 0; i < enabledSuites.length; i++) { + if (serverHello.cipher_suite + .equals(enabledSuites[i])) { + break find; + } + } + fatalAlert(AlertProtocol.HANDSHAKE_FAILURE, + "Bad server hello cipher suite"); + } + + if (isResuming) { + if (serverHello.session_id.length == 0) { + // server is not willing to establish the new connection + // using specified session + isResuming = false; + } else if (!Arrays.equals(serverHello.session_id, clientHello.session_id)) { + isResuming = false; + } else if (!session.protocol.equals(servProt)) { + fatalAlert(AlertProtocol.HANDSHAKE_FAILURE, + "Bad server hello protocol version"); + } else if (!session.cipherSuite + .equals(serverHello.cipher_suite)) { + fatalAlert(AlertProtocol.HANDSHAKE_FAILURE, + "Bad server hello cipher suite"); + } + if (serverHello.server_version[1] == 1) { + computerReferenceVerifyDataTLS("server finished"); + } else { + computerReferenceVerifyDataSSLv3(SSLv3Constants.server); + } + } + session.protocol = servProt; + recordProtocol.setVersion(session.protocol.version); + session.cipherSuite = serverHello.cipher_suite; + session.id = (byte[]) serverHello.session_id.clone(); + session.serverRandom = serverHello.random; + break; + case 11: // CERTIFICATE + if (serverHello == null || serverKeyExchange != null + || serverCert != null || isResuming) { + unexpectedMessage(); + return; + } + serverCert = new CertificateMessage(io_stream, length); + break; + case 12: // SERVER_KEY_EXCHANGE + if (serverHello == null || serverKeyExchange != null + || isResuming) { + unexpectedMessage(); + return; + } + serverKeyExchange = new ServerKeyExchange(io_stream, + length, session.cipherSuite.keyExchange); + break; + case 13: // CERTIFICATE_REQUEST + if (serverCert == null || certificateRequest != null + || session.cipherSuite.isAnonymous() || isResuming) { + unexpectedMessage(); + return; + } + certificateRequest = new CertificateRequest(io_stream, + length); + break; + case 14: // SERVER_HELLO_DONE + if (serverHello == null || serverHelloDone != null + || isResuming) { + unexpectedMessage(); + return; + } + serverHelloDone = new ServerHelloDone(io_stream, length); + if (this.nonBlocking) { + delegatedTasks.add(new DelegatedTask( + new PrivilegedExceptionAction(){ + public Object run() throws Exception { + processServerHelloDone(); + return null; + } + }, + this, + AccessController.getContext())); + return; + } + processServerHelloDone(); + break; + case 20: // FINISHED + if (!changeCipherSpecReceived) { + unexpectedMessage(); + return; + } + serverFinished = new Finished(io_stream, length); + verifyFinished(serverFinished.getData()); + session.lastAccessedTime = System.currentTimeMillis(); + parameters.getClientSessionContext().putSession(session); + if (isResuming) { + sendChangeCipherSpec(); + } else { + session.lastAccessedTime = System.currentTimeMillis(); + status = FINISHED; + } + // XXX there is no cleanup work + break; + default: + unexpectedMessage(); + return; + } + } catch (IOException e) { + // io stream dosn't contain complete handshake message + io_stream.reset(); + return; + } + } + + } + + /** + * Processes SSLv2 Hello message. + * SSLv2 client hello message message is an unexpected message + * for client side of handshake protocol. + * @ see TLS 1.0 spec., E.1. Version 2 client hello + * @param bytes + */ + public void unwrapSSLv2(byte[] bytes) { + unexpectedMessage(); + } + + /** + * Creates and sends Finished message + */ + protected void makeFinished() { + byte[] verify_data; + if (serverHello.server_version[1] == 1) { + verify_data = new byte[12]; + computerVerifyDataTLS("client finished", verify_data); + } else { + verify_data = new byte[36]; + computerVerifyDataSSLv3(SSLv3Constants.client, verify_data); + } + clientFinished = new Finished(verify_data); + send(clientFinished); + if (isResuming) { + session.lastAccessedTime = System.currentTimeMillis(); + status = FINISHED; + } else { + if (serverHello.server_version[1] == 1) { + computerReferenceVerifyDataTLS("server finished"); + } else { + computerReferenceVerifyDataSSLv3(SSLv3Constants.server); + } + status = NEED_UNWRAP; + } + } + + /** + * Processes ServerHelloDone: makes verification of the server messages; sends + * client messages, computers masterSecret, sends ChangeCipherSpec + */ + void processServerHelloDone() { + if (serverCert != null) { + if (session.cipherSuite.keyExchange == CipherSuite.KeyExchange_DH_anon + || session.cipherSuite.keyExchange == CipherSuite.KeyExchange_DH_anon_EXPORT) { + unexpectedMessage(); + return; + } + verifyServerCert(); + } else { + if (session.cipherSuite.keyExchange != CipherSuite.KeyExchange_DH_anon + && session.cipherSuite.keyExchange != CipherSuite.KeyExchange_DH_anon_EXPORT) { + unexpectedMessage(); + return; + } + } + + // Client certificate + if (certificateRequest != null) { + X509Certificate[] certs = null; + String clientAlias = ((X509ExtendedKeyManager) parameters + .getKeyManager()).chooseClientAlias(certificateRequest + .getTypesAsString(), + certificateRequest.certificate_authorities, null); + if (clientAlias != null) { + certs = ((X509ExtendedKeyManager) parameters.getKeyManager()) + .getCertificateChain((clientAlias)); + } + session.localCertificates = certs; + clientCert = new CertificateMessage(certs); + send(clientCert); + } + // Client key exchange + if (session.cipherSuite.keyExchange == CipherSuite.KeyExchange_RSA + || session.cipherSuite.keyExchange == CipherSuite.KeyExchange_RSA_EXPORT) { + // RSA encrypted premaster secret message + Cipher c; + try { + c = Cipher.getInstance("RSA/ECB/PKCS1Padding"); + if (serverKeyExchange != null) { + c.init(Cipher.ENCRYPT_MODE, serverKeyExchange + .getRSAPublicKey()); + } else { + c.init(Cipher.ENCRYPT_MODE, serverCert.certs[0]); + } + } catch (Exception e) { + fatalAlert(AlertProtocol.INTERNAL_ERROR, + "Unexpected exception", e); + return; + } + preMasterSecret = new byte[48]; + parameters.getSecureRandom().nextBytes(preMasterSecret); + System.arraycopy(clientHello.client_version, 0, preMasterSecret, 0, + 2); + try { + clientKeyExchange = new ClientKeyExchange(c + .doFinal(preMasterSecret), + serverHello.server_version[1] == 1); + } catch (Exception e) { + fatalAlert(AlertProtocol.INTERNAL_ERROR, + "Unexpected exception", e); + return; + } + } else { + PublicKey serverPublic; + KeyAgreement agreement = null; + DHParameterSpec spec; + try { + KeyFactory kf = null; + try { + kf = KeyFactory.getInstance("DH"); + } catch (NoSuchAlgorithmException e) { + kf = KeyFactory.getInstance("DiffieHellman"); + } + + try { + agreement = KeyAgreement.getInstance("DH"); + } catch (NoSuchAlgorithmException ee) { + agreement = KeyAgreement.getInstance("DiffieHellman"); + } + + KeyPairGenerator kpg = null; + try { + kpg = KeyPairGenerator.getInstance("DH"); + } catch (NoSuchAlgorithmException e) { + kpg = KeyPairGenerator.getInstance("DiffieHellman"); + } + if (serverKeyExchange != null) { + serverPublic = kf.generatePublic(new DHPublicKeySpec( + serverKeyExchange.par3, serverKeyExchange.par1, + serverKeyExchange.par2)); + spec = new DHParameterSpec(serverKeyExchange.par1, + serverKeyExchange.par2); + } else { + serverPublic = serverCert.certs[0].getPublicKey(); + spec = ((DHPublicKey) serverPublic).getParams(); + } + kpg.initialize(spec); + + KeyPair kp = kpg.generateKeyPair(); + Key key = kp.getPublic(); + if (clientCert != null + && serverCert != null + && (session.cipherSuite.keyExchange == CipherSuite.KeyExchange_DHE_RSA + || session.cipherSuite.keyExchange == CipherSuite.KeyExchange_DHE_DSS)) { + PublicKey client_pk = clientCert.certs[0].getPublicKey(); + PublicKey server_pk = serverCert.certs[0].getPublicKey(); + if (client_pk instanceof DHKey + && server_pk instanceof DHKey) { + if (((DHKey) client_pk).getParams().getG().equals( + ((DHKey) server_pk).getParams().getG()) + && ((DHKey) client_pk).getParams().getP() + .equals(((DHKey) server_pk).getParams().getG())) { + // client cert message DH public key parameters + // matched those specified by the + // server in its certificate, + clientKeyExchange = new ClientKeyExchange(); // empty + } + } + } else { + clientKeyExchange = new ClientKeyExchange( + ((DHPublicKey) key).getY()); + } + key = kp.getPrivate(); + agreement.init(key); + agreement.doPhase(serverPublic, true); + preMasterSecret = agreement.generateSecret(); + } catch (Exception e) { + fatalAlert(AlertProtocol.INTERNAL_ERROR, + "Unexpected exception", e); + return; + } + } + if (clientKeyExchange != null) { + send(clientKeyExchange); + } + + computerMasterSecret(); + + if (clientCert != null) { + boolean[] keyUsage = clientCert.certs[0].getKeyUsage(); + if (keyUsage != null && keyUsage[0]) { + // Certificate verify + DigitalSignature ds = new DigitalSignature( + session.cipherSuite.keyExchange); + if (session.cipherSuite.keyExchange == CipherSuite.KeyExchange_RSA_EXPORT + || session.cipherSuite.keyExchange == CipherSuite.KeyExchange_DHE_RSA + || session.cipherSuite.keyExchange == CipherSuite.KeyExchange_DHE_RSA_EXPORT) { + ds.setMD5(io_stream.getDigestMD5()); + ds.setSHA(io_stream.getDigestSHA()); + } else if (session.cipherSuite.keyExchange == CipherSuite.KeyExchange_DHE_DSS + || session.cipherSuite.keyExchange == CipherSuite.KeyExchange_DHE_DSS_EXPORT) { + ds.setSHA(io_stream.getDigestSHA()); + // The Signature should be empty in case of anonimous signature algorithm: + // } else if (session.cipherSuite.keyExchange == CipherSuite.KeyExchange_DH_anon || + // session.cipherSuite.keyExchange == CipherSuite.KeyExchange_DH_anon_EXPORT) { + } + certificateVerify = new CertificateVerify(ds.sign()); + send(certificateVerify); + } + } + + sendChangeCipherSpec(); + } + + /* + * Verifies certificate path + */ + private void verifyServerCert() { + String authType = null; + switch (session.cipherSuite.keyExchange) { + case 1: // KeyExchange_RSA + authType = "RSA"; + break; + case 2: // KeyExchange_RSA_EXPORT + if (serverKeyExchange != null ) { + // ephemeral RSA key is used + authType = "RSA_EXPORT"; + } else { + authType = "RSA"; + } + break; + case 3: // KeyExchange_DHE_DSS + case 4: // KeyExchange_DHE_DSS_EXPORT + authType = "DHE_DSS"; + break; + case 5: // KeyExchange_DHE_RSA + case 6: // KeyExchange_DHE_RSA_EXPORT + authType = "DHE_RSA"; + break; + case 7: // KeyExchange_DH_DSS + case 11: // KeyExchange_DH_DSS_EXPORT + authType = "DH_DSS"; + break; + case 8: // KeyExchange_DH_RSA + case 12: // KeyExchange_DH_RSA_EXPORT + authType = "DH_RSA"; + break; + case 9: // KeyExchange_DH_anon + case 10: // KeyExchange_DH_anon_EXPORT + return; + } + try { + parameters.getTrustManager().checkServerTrusted(serverCert.certs, + authType); + } catch (CertificateException e) { + fatalAlert(AlertProtocol.BAD_CERTIFICATE, "Not trusted server certificate", e); + return; + } + session.peerCertificates = serverCert.certs; + } + + /** + * Proceses ChangeCipherSpec message + */ + public void receiveChangeCipherSpec() { + if (isResuming) { + if (serverHello == null) { + unexpectedMessage(); + } + } else if (clientFinished == null) { + unexpectedMessage(); + } + changeCipherSpecReceived = true; + } + + // Find session to resume in client session context + private SSLSessionImpl findSessionToResume() { + String host; + int port; + if (engineOwner != null) { + host = engineOwner.getPeerHost(); + port = engineOwner.getPeerPort(); + } else { + host = socketOwner.getInetAddress().getHostName(); + port = socketOwner.getPort(); + } + if (host == null || port == -1) { + return null; // starts new session + } + + byte[] id; + SSLSession ses; + SSLSessionContext context = parameters.getClientSessionContext(); + for (Enumeration en = context.getIds(); en.hasMoreElements();) { + id = (byte[])en.nextElement(); + ses = context.getSession(id); + if (host.equals(ses.getPeerHost()) && port == ses.getPeerPort()) { + return (SSLSessionImpl)((SSLSessionImpl)ses).clone(); // resume + } + } + return null; // starts new session + } + +} Added: incubator/harmony/enhanced/classlib/trunk/modules/x-net/src/main/java/org/apache/harmony/security/provider/jsse/ClientHello.java URL: http://svn.apache.org/viewvc/incubator/harmony/enhanced/classlib/trunk/modules/x-net/src/main/java/org/apache/harmony/security/provider/jsse/ClientHello.java?rev=423275&view=auto ============================================================================== --- incubator/harmony/enhanced/classlib/trunk/modules/x-net/src/main/java/org/apache/harmony/security/provider/jsse/ClientHello.java (added) +++ incubator/harmony/enhanced/classlib/trunk/modules/x-net/src/main/java/org/apache/harmony/security/provider/jsse/ClientHello.java Tue Jul 18 15:50:12 2006 @@ -0,0 +1,209 @@ +/* + * Copyright 2006 The Apache Software Foundation or its licensors, as applicable. + * + * Licensed 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. + */ + +/** +* @author Boris Kuznetsov +* @version $Revision$ +*/ + +package org.apache.harmony.security.provider.jsse; + +import java.io.IOException; +import java.security.SecureRandom; +import java.util.Arrays; + +/** + * + * Represents Client Hello message + * @see TLS 1.0 spec., 7.4.1.2. Client hello + * (http://www.ietf.org/rfc/rfc2246.txt) + * + */ +public class ClientHello extends Message { + + /** + * Client version + */ + final byte[] client_version; + + /** + * Random bytes + */ + final byte[] random = new byte[32]; + + /** + * Session id + */ + final byte[] session_id; + + /** + * Cipher suites supported by the client + */ + final CipherSuite[] cipher_suites; + + /** + * Compression methods supported by the client + */ + final byte[] compression_methods; + + /** + * Creates outbound message + * @param sr + * @param version + * @param ses_id + * @param cipher_suite + */ + public ClientHello(SecureRandom sr, byte[] version, byte[] ses_id, + CipherSuite[] cipher_suite) { + client_version = version; + long gmt_unix_time = System.currentTimeMillis()/1000; + sr.nextBytes(random); + random[0] = (byte) (gmt_unix_time & 0xFF000000 >>> 24); + random[1] = (byte) (gmt_unix_time & 0xFF0000 >>> 16); + random[2] = (byte) (gmt_unix_time & 0xFF00 >>> 8); + random[3] = (byte) (gmt_unix_time & 0xFF); + session_id = ses_id; + this.cipher_suites = cipher_suite; + compression_methods = new byte[] { 0 }; // CompressionMethod.null + length = 38 + session_id.length + (this.cipher_suites.length << 1) + + compression_methods.length; + } + + /** + * Creates inbound message + * @param in + * @param length + * @throws IOException + */ + public ClientHello(HandshakeIODataStream in, int length) throws IOException { + client_version = new byte[2]; + client_version[0] = (byte) in.readUint8(); + client_version[1] = (byte) in.readUint8(); + in.read(random, 0, 32); + int size = in.read(); + session_id = new byte[size]; + in.read(session_id, 0, size); + int l = in.readUint16(); + if ((l & 0x01) == 0x01) { // cipher suites length must be an even number + fatalAlert(AlertProtocol.DECODE_ERROR, + "DECODE ERROR: incorrect ClientHello"); + } + size = l >> 1; + cipher_suites = new CipherSuite[size]; + for (int i = 0; i < size; i++) { + byte b0 = (byte) in.read(); + byte b1 = (byte) in.read(); + cipher_suites[i] = CipherSuite.getByCode(b0, b1); + } + size = in.read(); + compression_methods = new byte[size]; + in.read(compression_methods, 0, size); + this.length = 38 + session_id.length + (cipher_suites.length << 1) + + compression_methods.length; + if (this.length > length) { + fatalAlert(AlertProtocol.DECODE_ERROR, "DECODE ERROR: incorrect ClientHello"); + } + // for forward compatibility, extra data is permitted; + // must be ignored + if (this.length < length) { + in.skip(length - this.length); + this.length = length; + } + } + /** + * Parse V2ClientHello + * @param in + * @throws IOException + */ + public ClientHello(HandshakeIODataStream in) throws IOException { + if (in.readUint8() != 1) { + fatalAlert(AlertProtocol.DECODE_ERROR, "DECODE ERROR: incorrect V2ClientHello"); + } + client_version = new byte[2]; + client_version[0] = (byte) in.readUint8(); + client_version[1] = (byte) in.readUint8(); + int cipher_spec_length = in.readUint16(); + if (in.readUint16() != 0) { // session_id_length + // as client already knows the protocol known to a server it should + // initiate the connection in that native protocol + fatalAlert(AlertProtocol.DECODE_ERROR, + "DECODE ERROR: incorrect V2ClientHello, cannot be used for resuming"); + } + int challenge_length = in.readUint16(); + if (challenge_length < 16) { + fatalAlert(AlertProtocol.DECODE_ERROR, "DECODE ERROR: incorrect V2ClientHello, short challenge data"); + } + session_id = new byte[0]; + cipher_suites = new CipherSuite[cipher_spec_length/3]; + for (int i = 0; i < cipher_suites.length; i++) { + byte b0 = (byte) in.read(); + byte b1 = (byte) in.read(); + byte b2 = (byte) in.read(); + cipher_suites[i] = CipherSuite.getByCode(b0, b1, b2); + } + compression_methods = new byte[] { 0 }; // CompressionMethod.null + + if (challenge_length < 32) { + Arrays.fill(random, 0, 32 - challenge_length, (byte)0); + System.arraycopy(in.read(challenge_length), 0, random, 32 - challenge_length, challenge_length); + } else if (challenge_length == 32) { + System.arraycopy(in.read(32), 0, random, 0, 32); + } else { + System.arraycopy(in.read(challenge_length), challenge_length - 32, random, 0, 32); + } + if (in.available() > 0) { + fatalAlert(AlertProtocol.DECODE_ERROR, "DECODE ERROR: incorrect V2ClientHello, extra data"); + } + this.length = 38 + session_id.length + (cipher_suites.length << 1) + + compression_methods.length; + } + + /** + * Sends message + * @param out + */ + public void send(HandshakeIODataStream out) { + out.write(client_version); + out.write(random); + out.writeUint8(session_id.length); + out.write(session_id); + int size = cipher_suites.length << 1; + out.writeUint16(size); + for (int i = 0; i < cipher_suites.length; i++) { + out.write(cipher_suites[i].toBytes()); + } + out.writeUint8(compression_methods.length); + for (int i = 0; i < compression_methods.length; i++) { + out.write(compression_methods[i]); + } + } + + /** + * Returns client random + * @return client random + */ + public byte[] getRandom() { + return random; + } + + /** + * Returns message type + * @return + */ + public int getType() { + return Handshake.CLIENT_HELLO; + } +} Added: incubator/harmony/enhanced/classlib/trunk/modules/x-net/src/main/java/org/apache/harmony/security/provider/jsse/ClientKeyExchange.java URL: http://svn.apache.org/viewvc/incubator/harmony/enhanced/classlib/trunk/modules/x-net/src/main/java/org/apache/harmony/security/provider/jsse/ClientKeyExchange.java?rev=423275&view=auto ============================================================================== --- incubator/harmony/enhanced/classlib/trunk/modules/x-net/src/main/java/org/apache/harmony/security/provider/jsse/ClientKeyExchange.java (added) +++ incubator/harmony/enhanced/classlib/trunk/modules/x-net/src/main/java/org/apache/harmony/security/provider/jsse/ClientKeyExchange.java Tue Jul 18 15:50:12 2006 @@ -0,0 +1,151 @@ +/* + * Copyright 2006 The Apache Software Foundation or its licensors, as applicable. + * + * Licensed 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. + */ + +/** +* @author Boris Kuznetsov +* @version $Revision$ +*/ + +package org.apache.harmony.security.provider.jsse; + +import java.io.IOException; +import java.math.BigInteger; + +/** + * + * Represents client key exchange message + * @see TLS 1.0 spec., 7.4.7. Client key exchange message + * (http://www.ietf.org/rfc/rfc2246.txt) + * + */ +public class ClientKeyExchange extends Message { + + /** + * Exchange keys + */ + final byte[] exchange_keys; + + /** + * Equals true if TLS1.0 protocol is used + */ + boolean isTLS; + + /** + * Equals true if key exchange algorithm is RSA + */ + final boolean isRSA; + + /** + * Creates outbound message + * @param encrypted_pre_master_secret + * @param isTLS + */ + public ClientKeyExchange(byte[] encrypted_pre_master_secret, boolean isTLS) { + this.exchange_keys = encrypted_pre_master_secret; + length = this.exchange_keys.length; + if (isTLS) { + length += 2; + } + this.isTLS = isTLS; + isRSA = true; + } + + /** + * Creates outbound message + * @param dh_Yc + */ + public ClientKeyExchange(BigInteger dh_Yc) { + byte[] bb = dh_Yc.toByteArray(); + if (bb[0] == 0) { + exchange_keys = new byte[bb.length-1]; + System.arraycopy(bb, 1, exchange_keys, 0, exchange_keys.length); + } else { + exchange_keys = bb; + } + length = exchange_keys.length +2; + isRSA = false; + } + + /** + * Creates empty message + * + */ + public ClientKeyExchange() { + exchange_keys = new byte[0]; + length = 0; + isRSA = false; + } + + /** + * Creates inbound message + * @param length + * @param isTLS + * @param isRSA + * @throws IOException + */ + public ClientKeyExchange(HandshakeIODataStream in, int length, boolean isTLS, boolean isRSA) + throws IOException { + this.isTLS = isTLS; + this.isRSA = isRSA; + if (length == 0) { + this.length = 0; + exchange_keys = new byte[0]; + } else { + int size; + if (isRSA && !isTLS) {// SSL3.0 RSA + size = length; + this.length = size; + } else { // DH or TLSv1 RSA + size = in.readUint16(); + this.length = 2 + size; + } + exchange_keys = new byte[size]; + in.read(exchange_keys, 0, size); + if (this.length != length) { + fatalAlert(AlertProtocol.DECODE_ERROR, "DECODE ERROR: incorrect ClientKeyExchange"); + } + } + } + + /** + * Sends message + * @param out + */ + public void send(HandshakeIODataStream out) { + if (exchange_keys.length != 0) { + if (!isRSA || isTLS) {// DH or TLSv1 RSA + out.writeUint16(exchange_keys.length); + } + out.write(exchange_keys); + } + } + + /** + * Returns message type + * @return + */ + public int getType() { + return Handshake.CLIENT_KEY_EXCHANGE; + } + + /** + * Returns true if the message is empty (in case of implicit DH Yc) + * @return + */ + public boolean isEmpty() { + return (exchange_keys.length == 0); + } +} Added: incubator/harmony/enhanced/classlib/trunk/modules/x-net/src/main/java/org/apache/harmony/security/provider/jsse/ConnectionState.java URL: http://svn.apache.org/viewvc/incubator/harmony/enhanced/classlib/trunk/modules/x-net/src/main/java/org/apache/harmony/security/provider/jsse/ConnectionState.java?rev=423275&view=auto ============================================================================== --- incubator/harmony/enhanced/classlib/trunk/modules/x-net/src/main/java/org/apache/harmony/security/provider/jsse/ConnectionState.java (added) +++ incubator/harmony/enhanced/classlib/trunk/modules/x-net/src/main/java/org/apache/harmony/security/provider/jsse/ConnectionState.java Tue Jul 18 15:50:12 2006 @@ -0,0 +1,171 @@ +/* + * Copyright 2006 The Apache Software Foundation or its licensors, as applicable. + * + * Licensed 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. + */ + +/** + * @author Alexander Y. Kleymenov + * @version $Revision$ + */ + +package org.apache.harmony.security.provider.jsse; + +import javax.crypto.Cipher; + +/** + * This abstract class is a base for Record Protocol operating environmet + * of different SSL protocol versions. + */ +public abstract class ConnectionState { + + /** + * The cipher used for encode operations + */ + protected Cipher encCipher; + + /** + * The cipher used for decode operations + */ + protected Cipher decCipher; + + /** + * The cipher type + */ + protected boolean is_block_cipher; + + /** + * The size of MAC used under this connection state + */ + protected int hash_size; + + /** + * Write sequence number which is incremented after each + * encrypt call + */ + protected final byte[] write_seq_num = {0, 0, 0, 0, 0, 0, 0, 0}; + + /** + * Read sequence number which is incremented after each + * decrypt call + */ + protected final byte[] read_seq_num = {0, 0, 0, 0, 0, 0, 0, 0}; + + protected Logger.Stream logger = Logger.getStream("conn_state"); + + /** + * Returns the minimal possible size of the + * Generic[Stream|Generic]Cipher structure under this + * connection state. + */ + protected int getMinFragmentSize() { + // block ciphers return value with padding included + return encCipher.getOutputSize(1+hash_size); // 1 byte for data + } + + /** + * Returns the size of the Generic[Stream|Generic]Cipher structure + * corresponding to the content data of specified size. + */ + protected int getFragmentSize(int content_size) { + return encCipher.getOutputSize(content_size+hash_size); + } + + /** + * Returns the minimal upper bound of the content size enclosed + * into the Generic[Stream|Generic]Cipher structure of specified size. + * For stream ciphers the returned value will be exact value. + */ + protected int getContentSize(int generic_cipher_size) { + //it does not take the padding of block ciphered structures + //into account (so returned value can be greater than actual) + return decCipher.getOutputSize(generic_cipher_size)-hash_size; + } + + /** + * Creates the GenericStreamCipher or GenericBlockCipher + * data structure for specified data of specified type. + * @param type - the ContentType of the provided data + * @param fragment - the byte array containing the + * data to be encrypted under the current connection state. + */ + protected byte[] encrypt(byte type, byte[] fragment) { + return encrypt(type, fragment, 0, fragment.length); + } + + /** + * Creates the GenericStreamCipher or GenericBlockCipher + * data structure for specified data of specified type. + * @param type - the ContentType of the provided data + * @param fragment - the byte array containing the + * data to be encrypted under the current connection state. + * @param offset - the offset from which the data begins with. + * @param len - the length of the data. + */ + protected abstract byte[] encrypt + (byte type, byte[] fragment, int offset, int len); + + /** + * Retrieves the fragment of the Plaintext structure of + * the specified type from the provided data. + * @param type - the ContentType of the data to be decrypted. + * @param fragment - the byte array containing the + * data to be encrypted under the current connection state. + */ + protected byte[] decrypt(byte type, byte[] fragment) { + return decrypt(type, fragment, 0, fragment.length); + } + + /** + * Retrieves the fragment of the Plaintext structure of + * the specified type from the provided data. + * @param type - the ContentType of the data to be decrypted. + * @param fragment - the byte array containing the + * data to be encrypted under the current connection state. + * @param offset - the offset from which the data begins with. + * @param len - the length of the data. + */ + protected abstract byte[] decrypt + (byte type, byte[] fragment, int offset, int len); + + /** + * Increments the sequence number. + */ + protected static void incSequenceNumber(byte[] seq_num) { + int octet = 7; + while (octet >= 0) { + seq_num[octet] ++; + if (seq_num[octet] == 0) { + // characteristic overflow, so + // carrying a number in adding + octet --; + } else { + return; + } + } + } + + /** + * Shutdownes the protocol. It will be impossiblke to use the instance + * after the calling of this method. + */ + protected void shutdown() { + encCipher = null; + decCipher = null; + for (int i=0; i 0); + + 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 (is_block_cipher) { + 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 (is_block_cipher) { + 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."); + } + } + encCipher = Cipher.getInstance(algName); + decCipher = Cipher.getInstance(algName); + messageDigest = MessageDigest.getInstance(hashName); + 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); + mac_write_secret = client_mac_secret; + mac_read_secret = server_mac_secret; + } 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); + 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 occured. + */ + protected byte[] encrypt(byte type, byte[] fragment, int offset, int len) { + try { + int content_mac_length = len + hash_size; + int padding_length = is_block_cipher + ? padding_length = + ((8 - (++content_mac_length & 0x07)) & 0x07) + : 0; + 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 (is_block_cipher) { + // do padding: + Arrays.fill(res, content_mac_length-1, + res.length, (byte) (padding_length)); + } + if (logger != null) { + logger.println("SSLRecordProtocol.encrypt: " + + (is_block_cipher + ? "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. + */ + 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 (is_block_cipher) { + // check padding + int padding_length = data[data.length-1]; + for (int i=0; i> 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 0); + // do not count, as block_size is always 8 + // block_size = iv_size; + + 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 (is_block_cipher) { + 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 (is_block_cipher) { + 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()); + } + } + + encCipher = Cipher.getInstance(algName); + decCipher = Cipher.getInstance(algName); + encMac = Mac.getInstance(macName); + decMac = Mac.getInstance(macName); + + 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); + encMac.init(new SecretKeySpec(client_mac_secret, macName)); + decMac.init(new SecretKeySpec(server_mac_secret, macName)); + } 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.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 occured. + */ + protected byte[] encrypt(byte type, byte[] fragment, int offset, int len) { + try { + int content_mac_length = len + hash_size; + int padding_length = is_block_cipher + ? ((8 - (++content_mac_length & 0x07)) & 0x07) + : 0; + 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 (is_block_cipher) { + // do padding: + Arrays.fill(res, content_mac_length-1, + res.length, (byte) (padding_length)); + } + if (logger != null) { + logger.println("SSLRecordProtocol.do_encryption: Generic" + + (is_block_cipher + ? "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 occured. + */ + 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 (is_block_cipher) { + // check padding + int padding_length = data[data.length-1]; + for (int i=0; i> 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