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

org.voltdb.client.TLSHandshaker Maven / Gradle / Ivy

There is a newer version: 10.1.1
Show newest version
/* This file is part of VoltDB.
 * Copyright (C) 2008-2018 VoltDB Inc.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with VoltDB.  If not, see .
 */

package org.voltdb.client;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;

import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLEngineResult;
import javax.net.ssl.SSLEngineResult.HandshakeStatus;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSession;

import org.voltcore.network.CipherExecutor;

public class TLSHandshaker {
    private ByteBuffer m_rxNetData;
    private final SSLEngine m_eng;
    private final SocketChannel m_sc;
    private final int m_appsz;

    public TLSHandshaker(SocketChannel socketChan, SSLEngine engine) {
        m_sc = socketChan;
        m_eng = engine;
        SSLSession dummySession = engine.getSession();

        final int appsz = engine.getSession().getApplicationBufferSize() + 2048;
        if (Integer.highestOneBit(appsz) < appsz) {
            m_appsz = Integer.highestOneBit(appsz) << 1;
        } else {
            m_appsz = appsz;
        }
        m_rxNetData = (ByteBuffer)ByteBuffer.allocate(dummySession.getPacketBufferSize()).clear();
        dummySession.invalidate();
    }

    boolean canread(Selector selector) {

        Iterator keyIterator = selector.selectedKeys().iterator();
        boolean can = false;
        while(keyIterator.hasNext()) {
            SelectionKey key = keyIterator.next();
            if (key.isReadable()) {
                can = true;
            }
            keyIterator.remove();
        }
        return can;
    }

    public boolean handshake() throws IOException {
        ByteBuffer txNetData = (ByteBuffer)ByteBuffer.allocate(m_appsz).clear();
        ByteBuffer clearData = (ByteBuffer)ByteBuffer.allocate(CipherExecutor.FRAME_SIZE).clear();

        SSLEngineResult result = null;
        m_eng.beginHandshake();
        HandshakeStatus status = m_eng.getHandshakeStatus();
        boolean isBlocked = m_sc.isBlocking();

        synchronized (m_sc.blockingLock()) {
            isBlocked = m_sc.isBlocking();
            if (isBlocked) {
                m_sc.configureBlocking(false);
            }
        }
        Selector selector = Selector.open();
        m_sc.register(selector, SelectionKey.OP_READ);

        try {
            while (status != HandshakeStatus.FINISHED && status != HandshakeStatus.NOT_HANDSHAKING) {
                boolean waitForData = true;
                switch(status) {
                case NEED_UNWRAP:
                    if (waitForData && selector.select(2) == 1 && canread(selector)) {
                        if (m_sc.read(m_rxNetData) < 0) {
                            if (m_eng.isInboundDone() && m_eng.isOutboundDone()) {
                                return false;
                            }
                            try {
                                m_eng.closeInbound();
                            } catch (SSLException ingnoreIt) {
                            }

                            m_eng.closeOutbound();
                            status = m_eng.getHandshakeStatus();
                            break;
                        }
                    }
                    m_rxNetData.flip();
                    try {
                        result = m_eng.unwrap(m_rxNetData, clearData);
                        m_rxNetData.compact();
                        waitForData = m_rxNetData.position() == 0;
                        status = result.getHandshakeStatus();
                    } catch (SSLException e) {
                        m_eng.closeOutbound();
                        throw e;
                    }
                    switch (result.getStatus()) {
                    case OK:
                        break;
                    case BUFFER_OVERFLOW:
                        clearData = expand(clearData, false);
                        break;
                    case BUFFER_UNDERFLOW:
                        // During handshake, this indicates that there's not yet data to read.  We'll stay
                        // in this state until data shows up in m_rxNetData.
                        waitForData = true;
                       break;
                    case CLOSED:
                        if (m_eng.isOutboundDone()) {
                            return false;
                        } else {
                            m_eng.closeOutbound();
                            status = m_eng.getHandshakeStatus();
                        }
                        break;
                    default:
                        throw new IllegalStateException("Invalid SSL status: " + result.getStatus());
                    }
                    break;
                case NEED_WRAP:
                    txNetData.clear();
                    try {
                        result = m_eng.wrap(clearData, txNetData);
                        status = result.getHandshakeStatus();
                    } catch (SSLException e) {
                        m_eng.closeOutbound();
                        throw e;
                    }
                    switch(result.getStatus()) {
                    case OK:
                        txNetData.flip();
                        while (txNetData.hasRemaining()) {
                            m_sc.write(txNetData);
                        }
                        break;
                    case BUFFER_OVERFLOW:
                        txNetData = expand(txNetData, false);
                        break;
                    case BUFFER_UNDERFLOW:
                        throw new SSLException("Buffer underflow occured after a wrap");
                    case CLOSED:
                        txNetData.flip();
                        while (txNetData.hasRemaining()) {
                            m_sc.write(txNetData);
                        }
                        m_rxNetData.clear();
                        status = m_eng.getHandshakeStatus();
                        break;
                    default:
                        throw new IllegalStateException("Invalid SSL status: " + result.getStatus());
                    }
                    break;
                case NEED_TASK:
                    Runnable task;
                    while ((task=m_eng.getDelegatedTask()) != null) {
                        task.run();
                    }
                    status = m_eng.getHandshakeStatus();
                    break;
                case FINISHED:
                    break;
                case NOT_HANDSHAKING:
                    break;
                default:
                    throw new IllegalStateException("Invalid SSL handshake status" + status);
                }
            }
        } finally {
            SelectionKey sk = m_sc.keyFor(selector);
            sk.cancel();
            selector.close();
            if (isBlocked) {
                synchronized (m_sc.blockingLock()) {
                    m_sc.configureBlocking(isBlocked);
                }
            }
        }

        // Flip the buffer so it is setup in case there is a remnant
        m_rxNetData.flip();

        return true;
    }

    /**
     * The JDK caches SSL sessions when the participants are the same (i.e.
     * multiple connection requests from the same peer). Once a session is cached
     * the client side ends its handshake session quickly, and is able to send
     * the login Volt message before the server finishes its handshake. This message
     * is caught in the servers last handshake network read. This method returns the
     * login message unencrypted
     *
     * @return potentially a byte buffer containing the client login message
     * @throws IOException if the decryption operation fails
     */
    public ByteBuffer getRemnant() throws IOException {
        if (!m_rxNetData.hasRemaining()) {
            return m_rxNetData.slice();
        }
        ByteBuffer remnant = ByteBuffer.allocate(m_eng.getSession().getApplicationBufferSize());
        SSLEngineResult result = m_eng.unwrap(m_rxNetData, remnant);
        switch (result.getStatus()) {
        case OK:
            assert !m_rxNetData.hasRemaining() : "there are unexpected additional remnants";
            return ((ByteBuffer)remnant.flip()).slice().asReadOnlyBuffer();
        case BUFFER_OVERFLOW:
            throw new IOException("buffer underflow while decrypting handshake remnant");
        case BUFFER_UNDERFLOW:
            throw new IOException("buffer overflow while decrypting handshake remnant");
        case CLOSED:
            throw new IOException("ssl engine closed while decrypting handshake remnant");
        }
        return null; // unreachable
    }

    public boolean hasRemnant() {
        return m_rxNetData.hasRemaining();
    }

    public SSLEngine getSslEngine() {
        return m_eng;
    }

    private static ByteBuffer expand(ByteBuffer bb, boolean copy) {
        ByteBuffer expanded = (ByteBuffer)ByteBuffer.allocate(bb.capacity() <<1).clear();
        if (copy) {
            expanded.put((ByteBuffer)bb.flip());
        }
        return expanded;
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy