All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.mysql.cj.x.io.TlsDecryptingByteChannel Maven / Gradle / Ivy

There is a newer version: 8.0.33
Show newest version
/*
 * Copyright (c) 2016, 2017, Oracle and/or its affiliates. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License, version 2.0, as published by the
 * Free Software Foundation.
 *
 * This program is also distributed with certain software (including but not
 * limited to OpenSSL) that is licensed under separate terms, as designated in a
 * particular file or component or in included license documentation. The
 * authors of MySQL hereby grant you an additional permission to link the
 * program and your derivative works with the separately licensed software that
 * they have included with MySQL.
 *
 * Without limiting anything contained in the foregoing, this file, which is
 * part of MySQL Connector/J, is also subject to the Universal FOSS Exception,
 * version 1.0, a copy of which can be found at
 * http://oss.oracle.com/licenses/universal-foss-exception.
 *
 * This program 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, version 2.0,
 * for more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
 */

package com.mysql.cj.x.io;

import java.io.IOException;
import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousByteChannel;
import java.nio.channels.CompletionHandler;
import java.util.concurrent.Future;

import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLEngineResult;

import com.mysql.cj.core.exceptions.AssertionFailedException;

/**
 * FilterInputStream-esque byte channel that decrypts incoming packets. We proxy calls to the read method from the caller. We replace the provided completion
 * handler with our own handler that decrypts the incoming message and an then delegates to the original handler.
 *
 * 

* Note: This implementation does not support attachments for reads. They are not used in {@link AsyncMessageReader} which this class is in direct support * of. *

*/ public class TlsDecryptingByteChannel implements AsynchronousByteChannel, CompletionHandler { private static final ByteBuffer emptyBuffer = ByteBuffer.allocate(0); /** The underlying input stream. */ private AsynchronousByteChannel in; /** Encryption facility. */ private SSLEngine sslEngine; /** Buffer for cipher text data. This is where reads from the underlying channel will be directed to. */ private ByteBuffer cipherTextBuffer; /** Buffer for clear text data. This is where the SSLEngine will write the result of decrypting the cipher text buffer. */ private ByteBuffer clearTextBuffer; /** Handler for the next buffer received. */ private CompletionHandler handler; private ByteBuffer dst; /** * Create a new decrypting input stream. * * @param in * The underlying inputstream to read encrypted data from. * @param sslEngine * A configured {@link SSLEngine} which has already completed the handshake. */ public TlsDecryptingByteChannel(AsynchronousByteChannel in, SSLEngine sslEngine) { this.in = in; this.sslEngine = sslEngine; this.cipherTextBuffer = ByteBuffer.allocate(sslEngine.getSession().getPacketBufferSize()); this.cipherTextBuffer.flip(); this.clearTextBuffer = ByteBuffer.allocate(sslEngine.getSession().getApplicationBufferSize()); this.clearTextBuffer.flip(); } /** * Completion handler for a read. Prepare the buffer for decryption and continue with {@link #decryptAndDispatch()}. */ public void completed(Integer result, Void attachment) { if (result < 0) { CompletionHandler h = this.handler; this.handler = null; h.completed(result, null); return; } this.cipherTextBuffer.flip(); decryptAndDispatch(); } public void failed(Throwable exc, Void attachment) { CompletionHandler h = this.handler; this.handler = null; h.failed(exc, null); } /** * Handle the read callback from the underlying stream. Modulo error handling, we do the following: *
    *
  • Attempt to decrypt the current cipher text buffer.
  • *
  • If successful, deliver as much as possible to the client's completion handler.
  • *
  • If not successful, we will need to read more data to accumulate enough to decrypt. Issue a new read request.
  • *
*/ private synchronized void decryptAndDispatch() { try { this.clearTextBuffer.clear(); SSLEngineResult res = this.sslEngine.unwrap(this.cipherTextBuffer, this.clearTextBuffer); switch (res.getStatus()) { case BUFFER_UNDERFLOW: // Check if we need to enlarge the peer network packet buffer final int newPeerNetDataSize = this.sslEngine.getSession().getPacketBufferSize(); if (newPeerNetDataSize > this.cipherTextBuffer.capacity()) { // enlarge the peer network packet buffer ByteBuffer newPeerNetData = ByteBuffer.allocate(newPeerNetDataSize); newPeerNetData.put(this.cipherTextBuffer); newPeerNetData.flip(); this.cipherTextBuffer = newPeerNetData; } else { this.cipherTextBuffer.compact(); } // continue reading, not enough to decrypt yet this.in.read(this.cipherTextBuffer, null, this); return; case BUFFER_OVERFLOW: // not enough space in clearTextBuffer to decrypt packet. bug? throw new BufferOverflowException(); case OK: this.clearTextBuffer.flip(); dispatchData(); break; case CLOSED: this.handler.completed(-1, null); } } catch (Throwable ex) { failed(ex, null); } } /** * Main entry point from caller. */ public void read(ByteBuffer dest, A attachment, CompletionHandler hdlr) { try { if (this.handler != null) { hdlr.completed(0, null); } this.handler = hdlr; this.dst = dest; if (this.clearTextBuffer.hasRemaining()) { // copy any remaining data directly to client dispatchData(); } else if (this.cipherTextBuffer.hasRemaining()) { // otherwise, decrypt ciphertext data remaining from last time decryptAndDispatch(); } else { // otherwise, issue a new read request this.cipherTextBuffer.clear(); this.in.read(this.cipherTextBuffer, null, this); } } catch (Throwable ex) { hdlr.failed(ex, null); } } /** * Dispatch data to the caller's buffer and signal the completion handler. This represents the end of one completed read operation. The handler and * destination will be reset for the next request. */ private synchronized void dispatchData() { int transferred = Math.min(this.dst.remaining(), this.clearTextBuffer.remaining()); if (this.clearTextBuffer.remaining() > this.dst.remaining()) { // the ByteBuffer bulk copy only works if the src has <= remaining of the dst. narrow the view of src here to make use of i int newLimit = this.clearTextBuffer.position() + transferred; ByteBuffer src = this.clearTextBuffer.duplicate(); src.limit(newLimit); this.dst.put(src); this.clearTextBuffer.position(this.clearTextBuffer.position() + transferred); } else { this.dst.put(this.clearTextBuffer); } // use a temporary to allow caller to initiate a new read in the callback CompletionHandler h = this.handler; this.handler = null; if (this.in.isOpen()) { // If channel is still open then force the call through sun.nio.ch.Invoker to avoid deep levels of recursion. // If we directly call the handler, we may grow a huge stack when the caller only reads small portions of the buffer and issues a new read request. // The Invoker will dispatch the call on the thread pool for the AsynchronousSocketChannel this.in.read(TlsDecryptingByteChannel.emptyBuffer, null, new CompletionHandler() { public void completed(Integer result, Void attachment) { h.completed(transferred, null); } public void failed(Throwable t, Void attachment) { // There should be no way to get here as the read on empty buf will immediately direct control to the `completed' method t.printStackTrace(); // TODO log error normally instead of sysout h.failed(AssertionFailedException.shouldNotHappen(new Exception(t)), null); } }); } else { h.completed(transferred, null); } } public void close() throws IOException { this.in.close(); } public boolean isOpen() { return this.in.isOpen(); } /** * Unused. Should not be called. */ public Future read(ByteBuffer dest) { throw new UnsupportedOperationException("This channel does not support direct reads"); } /** * Unused. Should not be called. */ public void write(ByteBuffer src, A attachment, CompletionHandler hdlr) { hdlr.failed(new UnsupportedOperationException("This channel does not support writes"), null); } /** * Unused. Should not be called. */ public Future write(ByteBuffer src) { throw new UnsupportedOperationException("This channel does not support writes"); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy