| /* OutputSecurityParameters.java -- |
| Copyright (C) 2006 Free Software Foundation, Inc. |
| |
| This file is a part of GNU Classpath. |
| |
| GNU Classpath is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 2 of the License, or (at |
| your option) any later version. |
| |
| GNU Classpath 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 for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with GNU Classpath; if not, write to the Free Software |
| Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 |
| USA |
| |
| Linking this library statically or dynamically with other modules is |
| making a combined work based on this library. Thus, the terms and |
| conditions of the GNU General Public License cover the whole |
| combination. |
| |
| As a special exception, the copyright holders of this library give you |
| permission to link this library with independent modules to produce an |
| executable, regardless of the license terms of these independent |
| modules, and to copy and distribute the resulting executable under |
| terms of your choice, provided that you also meet, for each linked |
| independent module, the terms and conditions of the license of that |
| module. An independent module is a module which is not derived from |
| or based on this library. If you modify this library, you may extend |
| this exception to your version of the library, but you are not |
| obligated to do so. If you do not wish to do so, delete this |
| exception statement from your version. */ |
| |
| |
| package gnu.javax.net.ssl.provider; |
| |
| import gnu.classpath.debug.Component; |
| import gnu.classpath.debug.SystemLogger; |
| import gnu.java.security.util.ByteBufferOutputStream; |
| |
| import java.nio.ByteBuffer; |
| |
| import java.util.zip.DataFormatException; |
| import java.util.zip.Deflater; |
| |
| import javax.crypto.Cipher; |
| import javax.crypto.IllegalBlockSizeException; |
| import javax.crypto.Mac; |
| import javax.crypto.ShortBufferException; |
| |
| public class OutputSecurityParameters |
| { |
| private static final SystemLogger logger = SystemLogger.SYSTEM; |
| private final Cipher cipher; |
| private final Mac mac; |
| private final Deflater deflater; |
| private final SessionImpl session; |
| private final CipherSuite suite; |
| private long sequence; |
| |
| public OutputSecurityParameters (final Cipher cipher, final Mac mac, |
| final Deflater deflater, SessionImpl session, |
| CipherSuite suite) |
| { |
| this.cipher = cipher; |
| this.mac = mac; |
| this.deflater = deflater; |
| this.session = session; |
| this.suite = suite; |
| sequence = 0; |
| } |
| |
| /** |
| * Encrypt a record, storing the result in the given output buffer. |
| * |
| * @return The number of bytes taken from the input, and the number stored |
| * into `output;' that is, the size of the encrypted fragment, plus the |
| * encoding for the record. |
| */ |
| public int[] encrypt (final ByteBuffer[] input, int offset, int length, |
| final ContentType contentType, final ByteBuffer output) |
| throws DataFormatException, IllegalBlockSizeException, ShortBufferException |
| { |
| if (offset < 0 || offset >= input.length |
| || length <= 0 || offset + length > input.length) |
| throw new IndexOutOfBoundsException(); |
| |
| if (Debug.DEBUG) |
| for (int i = offset; i < offset+length; i++) |
| logger.logv(Component.SSL_RECORD_LAYER, "encrypting record [{0}]: {1}", |
| i-offset, input[i]); |
| |
| int maclen = 0; |
| if (mac != null) |
| maclen = session.isTruncatedMac() ? 10 : mac.getMacLength (); |
| |
| int ivlen = 0; |
| byte[] iv = null; |
| if (session.version.compareTo(ProtocolVersion.TLS_1_1) >= 0 |
| && !suite.isStreamCipher()) |
| { |
| ivlen = cipher.getBlockSize(); |
| iv = new byte[ivlen]; |
| session.random().nextBytes(iv); |
| } |
| |
| int padaddlen = 0; |
| if (!suite.isStreamCipher() |
| && session.version.compareTo(ProtocolVersion.TLS_1) >= 0) |
| { |
| padaddlen = (session.random().nextInt(255 / cipher.getBlockSize()) |
| * cipher.getBlockSize()); |
| } |
| |
| int fragmentLength = 0; |
| ByteBuffer[] fragments = null; |
| // Compress the content, if needed. |
| if (deflater != null) |
| { |
| ByteBufferOutputStream deflated = new ByteBufferOutputStream(); |
| |
| byte[] inbuf = new byte[1024]; |
| byte[] outbuf = new byte[1024]; |
| int written = 0; |
| |
| // Here we use the guarantee that the deflater won't increase the |
| // output size by more than 1K -- we resign ourselves to only deflate |
| // as much data as we have space for *uncompressed*, |
| int limit = output.remaining() - (maclen + ivlen + padaddlen) - 1024; |
| |
| for (int i = offset; i < length && written < limit; i++) |
| { |
| ByteBuffer in = input[i]; |
| while (in.hasRemaining() && written < limit) |
| { |
| int l = Math.min(in.remaining(), inbuf.length); |
| l = Math.min(limit - written, l); |
| in.get(inbuf, 0, l); |
| deflater.setInput(inbuf, 0, l); |
| l = deflater.deflate(outbuf); |
| deflated.write(outbuf, 0, l); |
| written += l; |
| } |
| } |
| deflater.finish(); |
| while (!deflater.finished()) |
| { |
| int l = deflater.deflate(outbuf); |
| deflated.write(outbuf, 0, l); |
| written += l; |
| } |
| fragments = new ByteBuffer[] { deflated.buffer() }; |
| fragmentLength = ((int) deflater.getBytesWritten()) + maclen + ivlen; |
| deflater.reset(); |
| offset = 0; |
| length = 1; |
| } |
| else |
| { |
| int limit = output.remaining() - (maclen + ivlen + padaddlen); |
| fragments = input; |
| for (int i = offset; i < length && fragmentLength < limit; i++) |
| { |
| int l = Math.min(limit - fragmentLength, fragments[i].remaining()); |
| fragmentLength += l; |
| } |
| fragmentLength += maclen + ivlen; |
| } |
| |
| // Compute padding... |
| int padlen = 0; |
| byte[] pad = null; |
| if (!suite.isStreamCipher()) |
| { |
| int bs = cipher.getBlockSize(); |
| padlen = bs - (fragmentLength % bs); |
| if (Debug.DEBUG) |
| logger.logv(Component.SSL_RECORD_LAYER, |
| "framentLen:{0} padlen:{1} blocksize:{2}", |
| fragmentLength, padlen, bs); |
| if (session.version.compareTo(ProtocolVersion.TLS_1) >= 0) |
| { |
| // TLS 1.0 and later uses a random amount of padding, up to |
| // 255 bytes. Each byte of the pad is equal to the padding |
| // length, minus one. |
| padlen += padaddlen; |
| while (padlen > 255) |
| padlen -= bs; |
| pad = new byte[padlen]; |
| for (int i = 0; i < padlen; i++) |
| pad[i] = (byte) (padlen - 1); |
| } |
| else |
| { |
| // SSL 3 uses a pad only as large as the block size, but the |
| // pad may contain any values. |
| pad = new byte[padlen]; |
| session.random().nextBytes(pad); |
| pad[padlen - 1] = (byte) (padlen - 1); |
| } |
| fragmentLength += pad.length; |
| } |
| |
| // If there is a MAC, compute it. |
| byte[] macValue = null; |
| if (mac != null) |
| { |
| mac.update((byte) (sequence >>> 56)); |
| mac.update((byte) (sequence >>> 48)); |
| mac.update((byte) (sequence >>> 40)); |
| mac.update((byte) (sequence >>> 32)); |
| mac.update((byte) (sequence >>> 24)); |
| mac.update((byte) (sequence >>> 16)); |
| mac.update((byte) (sequence >>> 8)); |
| mac.update((byte) sequence); |
| mac.update((byte) contentType.getValue()); |
| if (session.version != ProtocolVersion.SSL_3) |
| { |
| mac.update((byte) session.version.major ()); |
| mac.update((byte) session.version.minor ()); |
| } |
| int toWrite = fragmentLength - maclen - ivlen - padlen; |
| mac.update((byte) (toWrite >>> 8)); |
| mac.update((byte) toWrite); |
| int written = 0; |
| for (int i = offset; i < length && written < toWrite; i++) |
| { |
| ByteBuffer fragment = fragments[i].duplicate(); |
| int l = Math.min(fragment.remaining(), toWrite - written); |
| fragment.limit(fragment.position() + l); |
| mac.update(fragment); |
| } |
| macValue = mac.doFinal(); |
| } |
| |
| Record outrecord = new Record(output); |
| outrecord.setContentType(contentType); |
| outrecord.setVersion(session.version); |
| outrecord.setLength(fragmentLength); |
| |
| int consumed = 0; |
| ByteBuffer outfragment = outrecord.fragment(); |
| |
| if (cipher != null) |
| { |
| if (iv != null) |
| cipher.update(ByteBuffer.wrap(iv), outfragment); |
| int toWrite = fragmentLength - maclen - ivlen - padlen; |
| for (int i = offset; i < offset + length && consumed < toWrite; i++) |
| { |
| ByteBuffer fragment = fragments[i].slice(); |
| int l = Math.min(fragment.remaining(), toWrite - consumed); |
| fragment.limit(fragment.position() + l); |
| cipher.update(fragment, outfragment); |
| fragments[i].position(fragments[i].position() + l); |
| consumed += l; |
| } |
| if (macValue != null) |
| cipher.update(ByteBuffer.wrap(macValue), outfragment); |
| if (pad != null) |
| cipher.update(ByteBuffer.wrap(pad), outfragment); |
| } |
| else |
| { |
| // iv and pad are only used if we have a block cipher. |
| int toWrite = fragmentLength - maclen; |
| for (int i = offset; i < offset + length && consumed < toWrite; i++) |
| { |
| ByteBuffer fragment = fragments[i]; |
| int l = Math.min(fragment.remaining(), toWrite - consumed); |
| fragment.limit(fragment.position() + l); |
| outfragment.put(fragment); |
| consumed += l; |
| } |
| if (macValue != null) |
| outfragment.put(macValue); |
| } |
| |
| // Advance the output buffer's position. |
| output.position(output.position() + outrecord.length() + 5); |
| sequence++; |
| |
| return new int[] { consumed, fragmentLength + 5 }; |
| } |
| |
| CipherSuite suite() |
| { |
| return suite; |
| } |
| } |