1/*
2 * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package jdk.incubator.http;
27
28import java.io.IOException;
29import java.nio.ByteBuffer;
30import java.nio.channels.SocketChannel;
31import java.util.Arrays;
32import java.util.List;
33import java.util.concurrent.locks.Lock;
34import java.util.concurrent.locks.ReentrantLock;
35import javax.net.ssl.SSLEngineResult.HandshakeStatus;
36import javax.net.ssl.SSLEngineResult.Status;
37import javax.net.ssl.*;
38import jdk.incubator.http.internal.common.Log;
39import jdk.incubator.http.internal.common.Utils;
40import static javax.net.ssl.SSLEngineResult.HandshakeStatus.*;
41
42/**
43 * Implements the mechanics of SSL by managing an SSLEngine object.
44 * One of these is associated with each SSLConnection.
45 */
46class SSLDelegate {
47
48    final SSLEngine engine;
49    final EngineWrapper wrapper;
50    final Lock handshaking = new ReentrantLock();
51    final SSLParameters sslParameters;
52    final SocketChannel chan;
53    final HttpClientImpl client;
54    final String serverName;
55
56    SSLDelegate(SSLEngine eng, SocketChannel chan, HttpClientImpl client, String sn)
57    {
58        this.engine = eng;
59        this.chan = chan;
60        this.client = client;
61        this.wrapper = new EngineWrapper(chan, engine);
62        this.sslParameters = engine.getSSLParameters();
63        this.serverName = sn;
64    }
65
66    // alpn[] may be null
67    SSLDelegate(SocketChannel chan, HttpClientImpl client, String[] alpn, String sn)
68        throws IOException
69    {
70        serverName = sn;
71        SSLContext context = client.sslContext();
72        engine = context.createSSLEngine();
73        engine.setUseClientMode(true);
74        SSLParameters sslp = client.sslParameters()
75                                   .orElseGet(context::getSupportedSSLParameters);
76        sslParameters = Utils.copySSLParameters(sslp);
77        if (sn != null) {
78            SNIHostName sni = new SNIHostName(sn);
79            sslParameters.setServerNames(List.of(sni));
80        }
81        if (alpn != null) {
82            sslParameters.setApplicationProtocols(alpn);
83            Log.logSSL("SSLDelegate: Setting application protocols: {0}" + Arrays.toString(alpn));
84        } else {
85            Log.logSSL("SSLDelegate: No application protocols proposed");
86        }
87        engine.setSSLParameters(sslParameters);
88        wrapper = new EngineWrapper(chan, engine);
89        this.chan = chan;
90        this.client = client;
91    }
92
93    SSLParameters getSSLParameters() {
94        return sslParameters;
95    }
96
97    private static long countBytes(ByteBuffer[] buffers, int start, int number) {
98        long c = 0;
99        for (int i=0; i<number; i++) {
100            c+= buffers[start+i].remaining();
101        }
102        return c;
103    }
104
105
106    static class WrapperResult {
107        static WrapperResult createOK() {
108            WrapperResult r = new WrapperResult();
109            r.buf = null;
110            r.result = new SSLEngineResult(Status.OK, NOT_HANDSHAKING, 0, 0);
111            return r;
112        }
113        SSLEngineResult result;
114
115        ByteBuffer buf; // buffer containing result data
116    }
117
118    int app_buf_size;
119    int packet_buf_size;
120
121    enum BufType {
122        PACKET,
123        APPLICATION
124    }
125
126    ByteBuffer allocate (BufType type) {
127        return allocate (type, -1);
128    }
129
130    // TODO: Use buffer pool for this
131    ByteBuffer allocate (BufType type, int len) {
132        assert engine != null;
133        synchronized (this) {
134            int size;
135            if (type == BufType.PACKET) {
136                if (packet_buf_size == 0) {
137                    SSLSession sess = engine.getSession();
138                    packet_buf_size = sess.getPacketBufferSize();
139                }
140                if (len > packet_buf_size) {
141                    packet_buf_size = len;
142                }
143                size = packet_buf_size;
144            } else {
145                if (app_buf_size == 0) {
146                    SSLSession sess = engine.getSession();
147                    app_buf_size = sess.getApplicationBufferSize();
148                }
149                if (len > app_buf_size) {
150                    app_buf_size = len;
151                }
152                size = app_buf_size;
153            }
154            return ByteBuffer.allocate (size);
155        }
156    }
157
158    /* reallocates the buffer by :-
159     * 1. creating a new buffer double the size of the old one
160     * 2. putting the contents of the old buffer into the new one
161     * 3. set xx_buf_size to the new size if it was smaller than new size
162     *
163     * flip is set to true if the old buffer needs to be flipped
164     * before it is copied.
165     */
166    private ByteBuffer realloc (ByteBuffer b, boolean flip, BufType type) {
167        // TODO: there should be the linear growth, rather than exponential as
168        // we definitely know the maximum amount of space required to unwrap
169        synchronized (this) {
170            int nsize = 2 * b.capacity();
171            ByteBuffer n = allocate (type, nsize);
172            if (flip) {
173                b.flip();
174            }
175            n.put(b);
176            b = n;
177        }
178        return b;
179    }
180
181    /**
182     * This is a thin wrapper over SSLEngine and the SocketChannel, which
183     * guarantees the ordering of wraps/unwraps with respect to the underlying
184     * channel read/writes. It handles the UNDER/OVERFLOW status codes
185     * It does not handle the handshaking status codes, or the CLOSED status code
186     * though once the engine is closed, any attempt to read/write to it
187     * will get an exception.  The overall result is returned.
188     * It functions synchronously/blocking
189     */
190    class EngineWrapper {
191
192        SocketChannel chan;
193        SSLEngine engine;
194        Object wrapLock, unwrapLock;
195        ByteBuffer unwrap_src, wrap_dst;
196        boolean closed = false;
197        int u_remaining; // the number of bytes left in unwrap_src after an unwrap()
198
199        EngineWrapper (SocketChannel chan, SSLEngine engine) {
200            this.chan = chan;
201            this.engine = engine;
202            wrapLock = new Object();
203            unwrapLock = new Object();
204            unwrap_src = allocate(BufType.PACKET);
205            wrap_dst = allocate(BufType.PACKET);
206        }
207
208        void close () throws IOException {
209        }
210
211        WrapperResult wrapAndSend(ByteBuffer src, boolean ignoreClose)
212            throws IOException
213        {
214            ByteBuffer[] buffers = new ByteBuffer[1];
215            buffers[0] = src;
216            return wrapAndSend(buffers, 0, 1, ignoreClose);
217        }
218
219        /* try to wrap and send the data in src. Handles OVERFLOW.
220         * Might block if there is an outbound blockage or if another
221         * thread is calling wrap(). Also, might not send any data
222         * if an unwrap is needed.
223         */
224        WrapperResult wrapAndSend(ByteBuffer[] src,
225                                  int offset,
226                                  int len,
227                                  boolean ignoreClose)
228            throws IOException
229        {
230            if (closed && !ignoreClose) {
231                throw new IOException ("Engine is closed");
232            }
233            Status status;
234            WrapperResult r = new WrapperResult();
235            synchronized (wrapLock) {
236                wrap_dst.clear();
237                do {
238                    r.result = engine.wrap (src, offset, len, wrap_dst);
239                    status = r.result.getStatus();
240                    if (status == Status.BUFFER_OVERFLOW) {
241                        wrap_dst = realloc (wrap_dst, true, BufType.PACKET);
242                    }
243                } while (status == Status.BUFFER_OVERFLOW);
244                if (status == Status.CLOSED && !ignoreClose) {
245                    closed = true;
246                    return r;
247                }
248                if (r.result.bytesProduced() > 0) {
249                    wrap_dst.flip();
250                    int l = wrap_dst.remaining();
251                    assert l == r.result.bytesProduced();
252                    while (l>0) {
253                        l -= chan.write (wrap_dst);
254                    }
255                }
256            }
257            return r;
258        }
259
260        /* block until a complete message is available and return it
261         * in dst, together with the Result. dst may have been re-allocated
262         * so caller should check the returned value in Result
263         * If handshaking is in progress then, possibly no data is returned
264         */
265        WrapperResult recvAndUnwrap(ByteBuffer dst) throws IOException {
266            Status status;
267            WrapperResult r = new WrapperResult();
268            r.buf = dst;
269            if (closed) {
270                throw new IOException ("Engine is closed");
271            }
272            boolean needData;
273            if (u_remaining > 0) {
274                unwrap_src.compact();
275                unwrap_src.flip();
276                needData = false;
277            } else {
278                unwrap_src.clear();
279                needData = true;
280            }
281            synchronized (unwrapLock) {
282                int x;
283                do {
284                    if (needData) {
285                        x = chan.read (unwrap_src);
286                        if (x == -1) {
287                            throw new IOException ("connection closed for reading");
288                        }
289                        unwrap_src.flip();
290                    }
291                    r.result = engine.unwrap (unwrap_src, r.buf);
292                    status = r.result.getStatus();
293                    if (status == Status.BUFFER_UNDERFLOW) {
294                        if (unwrap_src.limit() == unwrap_src.capacity()) {
295                            /* buffer not big enough */
296                            unwrap_src = realloc (
297                                unwrap_src, false, BufType.PACKET
298                            );
299                        } else {
300                            /* Buffer not full, just need to read more
301                             * data off the channel. Reset pointers
302                             * for reading off SocketChannel
303                             */
304                            unwrap_src.position (unwrap_src.limit());
305                            unwrap_src.limit (unwrap_src.capacity());
306                        }
307                        needData = true;
308                    } else if (status == Status.BUFFER_OVERFLOW) {
309                        r.buf = realloc (r.buf, true, BufType.APPLICATION);
310                        needData = false;
311                    } else if (status == Status.CLOSED) {
312                        closed = true;
313                        r.buf.flip();
314                        return r;
315                    }
316                } while (status != Status.OK);
317            }
318            u_remaining = unwrap_src.remaining();
319            return r;
320        }
321    }
322
323    WrapperResult sendData (ByteBuffer src) throws IOException {
324        ByteBuffer[] buffers = new ByteBuffer[1];
325        buffers[0] = src;
326        return sendData(buffers, 0, 1);
327    }
328
329    /**
330     * send the data in the given ByteBuffer. If a handshake is needed
331     * then this is handled within this method. When this call returns,
332     * all of the given user data has been sent and any handshake has been
333     * completed. Caller should check if engine has been closed.
334     */
335    WrapperResult sendData (ByteBuffer[] src, int offset, int len) throws IOException {
336        WrapperResult r = WrapperResult.createOK();
337        while (countBytes(src, offset, len) > 0) {
338            r = wrapper.wrapAndSend(src, offset, len, false);
339            Status status = r.result.getStatus();
340            if (status == Status.CLOSED) {
341                doClosure ();
342                return r;
343            }
344            HandshakeStatus hs_status = r.result.getHandshakeStatus();
345            if (hs_status != HandshakeStatus.FINISHED &&
346                hs_status != HandshakeStatus.NOT_HANDSHAKING)
347            {
348                doHandshake(hs_status);
349            }
350        }
351        return r;
352    }
353
354    /**
355     * read data thru the engine into the given ByteBuffer. If the
356     * given buffer was not large enough, a new one is allocated
357     * and returned. This call handles handshaking automatically.
358     * Caller should check if engine has been closed.
359     */
360    WrapperResult recvData (ByteBuffer dst) throws IOException {
361        /* we wait until some user data arrives */
362        int mark = dst.position();
363        WrapperResult r = null;
364        int pos = dst.position();
365        while (dst.position() == pos) {
366            r = wrapper.recvAndUnwrap (dst);
367            dst = (r.buf != dst) ? r.buf: dst;
368            Status status = r.result.getStatus();
369            if (status == Status.CLOSED) {
370                doClosure ();
371                return r;
372            }
373
374            HandshakeStatus hs_status = r.result.getHandshakeStatus();
375            if (hs_status != HandshakeStatus.FINISHED &&
376                hs_status != HandshakeStatus.NOT_HANDSHAKING)
377            {
378                doHandshake (hs_status);
379            }
380        }
381        Utils.flipToMark(dst, mark);
382        return r;
383    }
384
385    /* we've received a close notify. Need to call wrap to send
386     * the response
387     */
388    void doClosure () throws IOException {
389        try {
390            handshaking.lock();
391            ByteBuffer tmp = allocate(BufType.APPLICATION);
392            WrapperResult r;
393            do {
394                tmp.clear();
395                tmp.flip ();
396                r = wrapper.wrapAndSend(tmp, true);
397            } while (r.result.getStatus() != Status.CLOSED);
398        } finally {
399            handshaking.unlock();
400        }
401    }
402
403    /* do the (complete) handshake after acquiring the handshake lock.
404     * If two threads call this at the same time, then we depend
405     * on the wrapper methods being idempotent. eg. if wrapAndSend()
406     * is called with no data to send then there must be no problem
407     */
408    @SuppressWarnings("fallthrough")
409    void doHandshake (HandshakeStatus hs_status) throws IOException {
410        boolean wasBlocking = false;
411        try {
412            wasBlocking = chan.isBlocking();
413            handshaking.lock();
414            chan.configureBlocking(true);
415            ByteBuffer tmp = allocate(BufType.APPLICATION);
416            while (hs_status != HandshakeStatus.FINISHED &&
417                   hs_status != HandshakeStatus.NOT_HANDSHAKING)
418            {
419                WrapperResult r = null;
420                switch (hs_status) {
421                    case NEED_TASK:
422                        Runnable task;
423                        while ((task = engine.getDelegatedTask()) != null) {
424                            /* run in current thread, because we are already
425                             * running an external Executor
426                             */
427                            task.run();
428                        }
429                        /* fall thru - call wrap again */
430                    case NEED_WRAP:
431                        tmp.clear();
432                        tmp.flip();
433                        r = wrapper.wrapAndSend(tmp, false);
434                        break;
435
436                    case NEED_UNWRAP:
437                        tmp.clear();
438                        r = wrapper.recvAndUnwrap (tmp);
439                        if (r.buf != tmp) {
440                            tmp = r.buf;
441                        }
442                        assert tmp.position() == 0;
443                        break;
444                }
445                hs_status = r.result.getHandshakeStatus();
446            }
447            Log.logSSL(getSessionInfo());
448            if (!wasBlocking) {
449                chan.configureBlocking(false);
450            }
451        } finally {
452            handshaking.unlock();
453        }
454    }
455
456    static void printParams(SSLParameters p) {
457        System.out.println("SSLParameters:");
458        if (p == null) {
459            System.out.println("Null params");
460            return;
461        }
462        for (String cipher : p.getCipherSuites()) {
463                System.out.printf("cipher: %s\n", cipher);
464        }
465        // JDK 8 EXCL START
466        for (String approto : p.getApplicationProtocols()) {
467                System.out.printf("application protocol: %s\n", approto);
468        }
469        // JDK 8 EXCL END
470        for (String protocol : p.getProtocols()) {
471                System.out.printf("protocol: %s\n", protocol);
472        }
473        if (p.getServerNames() != null) {
474            for (SNIServerName sname : p.getServerNames()) {
475                System.out.printf("server name: %s\n", sname.toString());
476            }
477        }
478    }
479
480    String getSessionInfo() {
481        StringBuilder sb = new StringBuilder();
482        String application = engine.getApplicationProtocol();
483        SSLSession sess = engine.getSession();
484        String cipher = sess.getCipherSuite();
485        String protocol = sess.getProtocol();
486        sb.append("Handshake complete alpn: ")
487                .append(application)
488                .append(", Cipher: ")
489                .append(cipher)
490                .append(", Protocol: ")
491                .append(protocol);
492        return sb.toString();
493    }
494}
495