    001    /**
    002     * Copyright (C) 2010, Skype Limited
    003     *
    004     * All intellectual property rights, including but not limited to copyrights,
    005     * trademarks and patents, as well as know how and trade secrets contained in,
    006     * relating to, or arising from the internet telephony software of
    007     * Skype Limited (including its affiliates, "Skype"), including without
    008     * limitation this source code, Skype API and related material of such
    009     * software proprietary to Skype and/or its licensors ("IP Rights") are and
    010     * shall remain the exclusive property of Skype and/or its licensors.
    011     * The recipient hereby acknowledges and agrees that any unauthorized use of
    012     * the IP Rights is a violation of intellectual property laws.
    013     *
    014     * Skype reserves all rights and may take legal action against infringers of
    015     * IP Rights.
    016     *
    017     * The recipient agrees not to remove, obscure, make illegible or alter any
    018     * notices or indications of the IP Rights and/or Skype's rights and
    019     * ownership thereof.
    020     */
    021
    022    package com.skype.ipc;
    023
    024    import java.io.File;
    025    import java.io.FileNotFoundException;
    026    import java.io.FileOutputStream;
    027    import java.io.IOException;
    028    import java.net.Socket;
    029    import java.nio.ByteBuffer;
    030    import java.security.KeyManagementException;
    031    import java.security.NoSuchAlgorithmException;
    032    import java.security.Principal;
    033    import java.security.PrivateKey;
    034    import java.security.cert.CertificateEncodingException;
    035    import java.security.cert.CertificateException;
    036    import java.security.cert.X509Certificate;
    037    import java.util.LinkedList;
    038    import java.lang.reflect.Method;
    039
    040    import javax.net.ssl.KeyManager;
    041    import javax.net.ssl.SSLContext;
    042    import javax.net.ssl.SSLEngine;
    043    import javax.net.ssl.SSLEngineResult;
    044    import javax.net.ssl.SSLEngineResult.HandshakeStatus;
    045    import javax.net.ssl.SSLEngineResult.Status;
    046    import javax.net.ssl.SSLException;
    047    import javax.net.ssl.SSLParameters;
    048    import javax.net.ssl.SSLSession;
    049    import javax.net.ssl.TrustManager;
    050    import javax.net.ssl.X509ExtendedKeyManager;
    051    import javax.net.ssl.X509TrustManager;
    052
    053    import com.skype.util.Log;
    054
    055    /**
    056     * @author kcjones
    057     *
    058     */
    059    public class TLSServerTransport implements Transport
    060    {
    061            private static final String TAG = "TLSServerTransport";
    062            private static int MAXBUF;
    063
    064            private boolean connected = false;
    065            private Transport transport = null;
    066            private SSLEngine engine = null;
    067            private SSLContext context = null;
    068        private FileOutputStream logFileOut = null;
    069        private FileOutputStream logFileIn = null;
    070        private boolean isLogging = false;
    071
    072            private X509ExtendedKeyManager keyManager = null;
    073            private TrustManager trustManager = null;
    074
    075            private byte decrypted_bytes[] = new byte[]{};
    076            private LinkedList<byte[]> decrypted_chunks = new LinkedList<byte[]>();
    077        private ByteBuffer raw_source = null;
    078
    079        public TLSServerTransport(Transport t, X509Certificate cert, PrivateKey privKey)
    080        {
    081            transport = t;
    082
    083            trustManager = new dummyTrustManager();
    084            keyManager = new dummyKeyManager(cert,privKey);
    085            decrypted_chunks.clear();
    086        }
    087
    088            protected void finalize() throws Throwable
    089            {
    090                super.finalize();
    091                if (isLogging) {
    092                    logFileOut.flush();
    093                    logFileOut.close();
    094                    logFileIn.flush();
    095                    logFileIn.close();
    096                }
    097            }
    098
    099            @Override
    100            public void startLogging(String logFileName)
    101            {
    102                assert(!isLogging);
    103
    104            if (logFileName != null && ! logFileName.isEmpty()) {
    105                try {
    106                    logFileOut = new FileOutputStream(new File(logFileName + ".out.bin"), false);
    107                    logFileIn  = new FileOutputStream(new File(logFileName + ".in.bin"), false);
    108                    isLogging = true;
    109                    Log.d(TAG, "Transport logging started");
    110                }
    111                catch (FileNotFoundException e) {
    112                    Log.e(TAG, "Unable to open transport log files using:" + logFileName);
    113                }
    114            }
    115            }
    116
    117            void do_handshake() throws SSLException, IOException
    118            {
    119
    120                    Log.d(TAG, "do_handshake()");
    121                    SSLEngineResult engineResult = null;
    122                    HandshakeStatus result =  HandshakeStatus.NEED_UNWRAP;
    123                    while(true) {
    124                        switch(result) {
    125                        case FINISHED:
    126                            Log.d(TAG, result.toString());
    127                            return;
    128                        case NEED_TASK:
    129                            Log.d(TAG, result.toString());
    130                            Runnable task;
    131                            while((task=engine.getDelegatedTask()) != null) {
    132                                task.run();
    133                            }
    134                            break;
    135                        case NEED_UNWRAP: {
    136
    137                            byte [] msg = new byte [16384];
    138                            int idx = 0;
    139                            Log.d(TAG, result.toString());
    140
    141                            if (decrypted_bytes.length == 0) {
    142                                try {
    143                                    transport.peek();
    144                                    while(transport.hasMore() ) {
    145                                        msg[idx++] = (byte) transport.read();
    146                                    }
    147                                }
    148                                catch (IOException e) {
    149                                    Log.e(TAG, "Read exception:" + e.getMessage() + ":" + e.getCause());
    150                                    throw e;
    151                                }
    152                            }
    153                            else {
    154                                idx = decrypted_bytes.length;
    155                                System.arraycopy(decrypted_bytes, 0, msg, 0, idx);
    156                                decrypted_bytes = new byte[]{};
    157                            }
    158
    159                            ByteBuffer src0 = ByteBuffer.wrap(msg);
    160                            ByteBuffer dst0 = ByteBuffer.allocate(16384);
    161                            engineResult = engine.unwrap(src0, dst0);
    162                            result = engineResult.getHandshakeStatus();
    163                            int bytesConsumed = engineResult.bytesConsumed();
    164
    165                            int sum = 0; //check if we had trailing zeroes
    166                            while(bytesConsumed < idx)
    167                                sum |= msg[bytesConsumed++];
    168
    169                            int bytesLeftOver = idx - engineResult.bytesConsumed();
    170                            if (sum != 0 && bytesLeftOver > 0 ) { //more than one message, keep a copy
    171                                decrypted_bytes = new byte[bytesLeftOver];
    172                                System.arraycopy(msg, engineResult.bytesConsumed(),
    173                                        decrypted_bytes, 0, bytesLeftOver);
    174                            }
    175                            break;
    176                        }
    177                        case NEED_WRAP: {
    178                            Log.d(TAG, result.toString());
    179                            ByteBuffer EMPTY = ByteBuffer.allocate(0);
    180                            ByteBuffer dst1 = ByteBuffer.allocate(65536);
    181                            engineResult = engine.wrap(EMPTY, dst1);
    182                            byte [] bytes2send = new byte[engineResult.bytesProduced()];
    183                            dst1.rewind();
    184                            dst1.get(bytes2send);
    185                            transport.write(bytes2send.length , bytes2send);
    186                            break;
    187                        }
    188                        case NOT_HANDSHAKING:
    189                            Log.d(TAG, "discarding " + decrypted_bytes.length + " bytes");
    190                            if (decrypted_bytes.length > 0) {
    191                                ByteBuffer s = ByteBuffer.wrap(decrypted_bytes);
    192                                Log.hexdump(s.array());
    193                                decrypted_bytes = new byte[] {};
    194                            }
    195                            Log.d(TAG, result.toString());
    196                            return;
    197                        }
    198                        result = engine.getHandshakeStatus();
    199
    200            }
    201            }
    202
    203            void checkConnected() throws IOException
    204            {
    205                    if (!connected)
    206                            throw new IOException("TLSServerTransport not connected!");
    207            }
    208
    209            @Override
    210            public boolean connect() throws IOException
    211            {
    212                    Log.d(TAG, "connect()");
    213
    214                    if (!transport.connect())
    215                            return false;
    216
    217                    try {
    218                            context = SSLContext.getInstance("TLS");
    219                    } catch (NoSuchAlgorithmException e) {
    220                            throw new IOException("TLSServerTransport:IOException:" + e.getMessage() );
    221                    }
    222                    KeyManager [] km = { keyManager };
    223                    TrustManager [] tm = { trustManager };
    224                    try {
    225                            context.init(km, tm, null);
    226                    } catch (KeyManagementException e) {
    227                            throw new IOException("TLSServerTransport:KeyManagementException:" + e.getMessage() );
    228                    }
    229                    engine = context.createSSLEngine();
    230                    engine.setUseClientMode(false);
    231                    engine.setNeedClientAuth(false);
    232                    engine.setWantClientAuth(false);
    233
    234                    try {
    235                            @SuppressWarnings("rawtypes")
    236                Class c = Class.forName("javax.net.ssl.SSLEngine");
    237                            for (Method m : c.getDeclaredMethods() ) {
    238                                    if (m.getName() == "getSSLParameters") { //this is not supported on Android < 2.3
    239                                            SSLParameters params = engine.getSSLParameters();
    240                                            params.setCipherSuites(new String [] {
    241                                                    "TLS_RSA_WITH_AES_128_CBC_SHA"
    242                                            });
    243                                            engine.setSSLParameters(params);
    244                                            }
    245                                    }
    246                            }
    247                    catch (ClassNotFoundException e) { //shouldnt ever happen
    248                    }
    249
    250                    try {
    251                            do_handshake();
    252                    }
    253                    catch (SSLException e) {
    254                        throw new IOException("TLSServerTransport:handshake_failed:" + e.getMessage(), e);
    255                    }
    256                    catch (IOException e) {
    257                        throw new IOException("TLSServerTransport:handshake_failed:" + e.getMessage(), e);
    258                    }
    259                    connected = true;
    260
    261                    X509Certificate cert = keyManager.getCertificateChain("dummyServer")[0];
    262                    byte[] certBytes = null;
    263                    try {
    264                            certBytes = cert.getEncoded();
    265                    } catch (CertificateEncodingException e) {
    266                            e.printStackTrace();
    267                    }
    268                    String lenStr = String.format("%08X", certBytes.length);
    269                    byte [] lenBytes = lenStr.getBytes();
    270                    write(lenBytes.length,lenBytes);
    271                    write(certBytes.length,certBytes);
    272
    273                    return true;
    274            }
    275
    276            @Override
    277            public void disconnect() throws IOException
    278            {
    279                    checkConnected();
    280
    281                    transport.disconnect();
    282            }
    283
    284            @Override
    285            public boolean hasMore() throws IOException
    286            {
    287                    checkConnected();
    288                    Log.d(TAG, "hasMore() bytes:" + decrypted_bytes.length + " chunks:" + decrypted_chunks.size());
    289
    290                    return (decrypted_bytes.length > 0) || decrypted_chunks.size() > 0 || transport.hasMore();
    291            }
    292
    293            @Override
    294            public boolean isConnected() throws IOException
    295            {
    296                    return transport.isConnected();
    297            }
    298
    299            @Override
    300            public int peek() throws IOException
    301            {
    302    //          Log.d(TAG, "TLSServerTransport::peek() called");
    303                checkConnected();
    304
    305                if (decrypted_chunks.isEmpty()) {
    306                    readChunks();
    307                }
    308                if (decrypted_chunks.isEmpty())
    309                {
    310                    Log.d(TAG, "TLS Peek - nothing to peek at...");
    311                    return 0;
    312                }
    313    //          Log.d(TAG, "TLS Peek: " + Integer.toHexString(decrypted_chunks.getFirst()[0] & 0xFF) +
    314    //                  " " + (char)decrypted_chunks.getFirst()[0] +
    315    //                  " " + (int)(decrypted_chunks.getFirst()[0] & 0xFF));
    316                return decrypted_chunks.getFirst()[0];
    317            }
    318
    319            @Override
    320            public int read() throws IOException
    321            {
    322                    checkConnected();
    323                    byte b[] = new byte[]{'0'};
    324                    read(1,b);
    325    //              Log.d(TAG, "returning byte:" + b);
    326                    return b[0];
    327            }
    328
    329            @Override
    330            public int read(int numBytes, byte[] bytes) throws IOException
    331            {
    332                    checkConnected();
    333
    334                    int num_waits = 0;
    335                    final int MAXWAITS = 10;
    336                    final int WAITMILIS = 50; // how long should we wait?
    337                    int read = 0;
    338
    339                    while (read < numBytes) {
    340                            while (decrypted_chunks.isEmpty())
    341                            {
    342                                readChunks();
    343
    344                                if (decrypted_chunks.isEmpty()) {
    345                                    if (++num_waits > MAXWAITS)
    346                                        throw new IOException("timeout waiting for transport input");
    347
    348                                    try {
    349                                        Log.d(TAG, "Waiting for data on transport take:" + num_waits);
    350                                        wait(WAITMILIS);
    351                                    }
    352                                    catch (InterruptedException e) {}
    353                                }
    354                            }
    355
    356                            byte [] chunk = decrypted_chunks.removeFirst();
    357
    358                            if ((read+chunk.length) <= numBytes) {
    359                                    // copy decrypted bytes into caller's byte array
    360                                    System.arraycopy(chunk, 0, bytes, read, chunk.length);
    361                                    read += chunk.length;
    362                            } else {
    363                                // unfortunately, our readers do not request the input in chunks
    364                                // instead they read 1 byte increments which means we have to pop
    365                                // the top chunk of input, peel one byte off, and push the remains back onto the list.
    366                                // sub-optimal but works for now.
    367                                System.arraycopy(chunk, 0, bytes, read, numBytes-read);
    368                                byte [] chunkless = new byte[chunk.length - numBytes+read];
    369                                System.arraycopy(chunk, numBytes-read, chunkless, 0, chunk.length - numBytes+read);
    370                                decrypted_chunks.addFirst(chunkless);
    371                                read = numBytes;
    372                            }
    373                    }
    374                    return numBytes;
    375            }
    376
    377            @Override
    378            public int read(int numBytes, byte[] bytes, boolean needNumBytes) throws IOException {
    379                    Log.d(TAG, "TLSServerTransport::read(3) called");
    380                    int bytesRead = read(numBytes, bytes);
    381                    if ((bytesRead != numBytes) && (needNumBytes))
    382                            throw new IOException("TLSServerTransport::read() - read less than required number of bytes");
    383                    return bytesRead;
    384            }
    385
    386            /**
    387             * drain all input from transport stream and decrypt using engine.
    388             *
    389             * @param raw_bytes - buffer to accept unwrapped bytes read from socket
    390             * @throws IOException - when read buffer is too small, or other socket read exceptions thrown
    391             */
    392            protected void readChunks() throws IOException
    393            {
    394
    395                    boolean hadLeftovers = false;
    396
    397                    // allocate byte buffers to match session parameters exactly
    398                    if (raw_source == null) {
    399                        SSLSession s = engine.getSession();
    400                        MAXBUF = s.getPacketBufferSize();
    401                        if (s.getApplicationBufferSize() > MAXBUF) {
    402                            MAXBUF = s.getApplicationBufferSize();
    403                        }
    404                        Log.d(TAG, "SSLSession packet size:" + MAXBUF);
    405                        raw_source = ByteBuffer.allocate(MAXBUF);
    406                    }
    407
    408                    int pos = raw_source.position();
    409                    if (pos > 0) {
    410                        Log.d(TAG, "UNDERFLOW -contining from position:" + pos);
    411                        hadLeftovers = true;
    412                    }
    413
    414                    do {
    415                        raw_source.put((byte)transport.read());
    416                    } while( transport.hasMore() &&
    417                            raw_source.position() < MAXBUF );
    418
    419                    if (pos == raw_source.position()) {
    420                        Log.d(TAG, "Nothing read.");
    421                        return;
    422                    }
    423                    Log.d(TAG, "readChunks() done reading at position:" + raw_source.position());
    424
    425
    426                    ByteBuffer dst = ByteBuffer.allocate(MAXBUF);
    427                    boolean unwrapped;
    428                    int loop = 0;
    429                    do {
    430
    431                            int todecode = raw_source.remaining();
    432                            SSLEngineResult engineResult;
    433                            try {
    434                                    raw_source.flip();
    435                                    engineResult = engine.unwrap(raw_source, dst);
    436                                    raw_source.compact();
    437                            } catch (SSLException e) {
    438                                    String msg = "unwrap failed at loop " + loop + " with todecode " + todecode + "bytes "+engine.getSession().getCipherSuite();
    439                                    System.err.println(msg);
    440                                    Log.d(TAG, msg);
    441                                    throw e;
    442                            }
    443
    444                        SSLEngineResult.Status unwrap_status = engineResult.getStatus();
    445                        Log.d(TAG, "unwrap status:" + unwrap_status);
    446
    447                        unwrapped = (unwrap_status == SSLEngineResult.Status.OK);
    448                        dst.rewind();
    449                        if (unwrapped || unwrap_status == SSLEngineResult.Status.BUFFER_UNDERFLOW)
    450                        {
    451                            int l = engineResult.bytesProduced();
    452                            if (l > 0) {
    453                                 Log.d(TAG, "decrypted length:" + l);
    454                                 byte [] chunk = new byte[l];
    455                                 dst.get(chunk, 0, l);
    456
    457                                 Log.d(TAG, "\nchunk read is :");
    458                                 Log.hexdump(chunk);
    459                                 if (isLogging)
    460                                     logFileIn.write(chunk);
    461
    462                                 decrypted_chunks.addLast(chunk);
    463                            }
    464                        }
    465                        else if (unwrap_status != Status.BUFFER_UNDERFLOW) {
    466                            throw new IOException("Unrecoverable TLS error: " + unwrap_status);
    467                        }
    468                        else {
    469                            Log.d(TAG, "UNDERFLOW? reseting raw_source for next time.");
    470                        }
    471                        dst.rewind();
    472                    } while (unwrapped && raw_source.hasRemaining());
    473
    474                Log.d(TAG, "readChunks() done, hasRemaining:" + raw_source.hasRemaining());
    475
    476                if (!raw_source.hasRemaining()) {
    477                    raw_source.clear();
    478                }
    479            }
    480
    481            private void dumpSourceBuffer(String tag)
    482            {
    483                int pos = raw_source.position();
    484                int lim =raw_source.limit();
    485            Log.e(TAG, "---" + tag + " position:" + pos + " limit:" +  lim);
    486
    487            if (raw_source.hasArray() && pos < lim) {
    488                byte [] src = raw_source.array();
    489                byte [] section = new byte[lim - pos];
    490                System.arraycopy(src, pos, section, 0, lim - pos);
    491                Log.hexdump(section);
    492            }
    493            }
    494
    495            @Override
    496            public boolean write(byte b) throws IOException
    497            {
    498                    Log.e(TAG, "FAIL! write(" + b + ")");
    499                    throw new IOException("Invalid use of TLS transport");
    500            }
    501
    502            @Override
    503            public boolean write(int numBytes, byte[] bytes)
    504            throws IOException
    505            {
    506    //          synchronized (sslEngineLock) {
    507
    508                    // Log.d(TAG, "write(" + numBytes +"):");
    509                    // Log.hexdump(bytes);
    510
    511                    checkConnected();
    512
    513                    if (isLogging) {
    514                        logFileOut.write(bytes, 0, numBytes);
    515                    }
    516                    SSLSession s            = engine.getSession();
    517                    int begin = 0;
    518                    int applicationBufferSize   = s.getApplicationBufferSize();
    519                    int packetBufferSize        = s.getPacketBufferSize();
    520                    ByteBuffer dst = ByteBuffer.allocate(packetBufferSize);
    521                    while (numBytes > 0) {
    522                            ByteBuffer src = ByteBuffer.wrap(bytes,begin,numBytes > applicationBufferSize ? applicationBufferSize : numBytes);
    523                            SSLEngineResult engineResult = engine.wrap(src, dst);
    524                            byte [] bytes2send = new byte[engineResult.bytesProduced()];
    525                            dst.rewind();
    526                            dst.get(bytes2send);
    527                            numBytes -= engineResult.bytesConsumed();
    528                            begin    += engineResult.bytesConsumed();
    529                            transport.write(bytes2send.length, bytes2send);
    530                            dst.clear();
    531                    }
    532
    533    //          }
    534
    535                return false;
    536            }
    537
    538            public class dummyKeyManager extends X509ExtendedKeyManager {
    539                    X509Certificate cert;
    540                    PrivateKey privKey;
    541
    542                    public dummyKeyManager(X509Certificate _cert,PrivateKey _privKey) {
    543                            privKey = _privKey;
    544                            cert = _cert;
    545                    }
    546
    547                    @Override
    548                    public String chooseClientAlias(String[] arg0, Principal[] arg1, Socket arg2) {
    549                            Log.d(TAG, "chooseClientAlias");
    550                            return null;
    551                    }
    552
    553                    public String chooseEngineServerAlias(String keyType, Principal[] issuers, SSLEngine engine) {
    554                            Log.d(TAG, "chooseEngineServerAlias " + keyType);
    555                            if (keyType == "RSA") {
    556                                    return "dummyServer";
    557                            }
    558                            return null;
    559                    }
    560
    561                    @Override
    562                    public String chooseServerAlias(String keyType, Principal[] issuers, Socket arg2) {
    563                            Log.d(TAG, "chooseServerAlias");
    564                            return null;
    565                    }
    566
    567                    @Override
    568                    public X509Certificate[] getCertificateChain(String arg0) {
    569                            Log.d(TAG, "getCertificateChain");
    570                            return new X509Certificate[] { cert };
    571                    }
    572
    573                    @Override
    574                    public String[] getClientAliases(String arg0, Principal[] arg1) {
    575                            Log.d(TAG, "getClientAliases");
    576                            return null;
    577                    }
    578
    579                    @Override
    580                    public PrivateKey getPrivateKey(String arg0) {
    581                            Log.d(TAG, "getPrivateKey");
    582                            return privKey;
    583                    }
    584
    585                    @Override
    586                    public String[] getServerAliases(String arg0, Principal[] arg1) {
    587                            Log.d(TAG, "getServerAliases");
    588                            return null;
    589                    }
    590            }
    591
    592            /*
    593             * This class should never be called
    594             * */
    595            public class dummyTrustManager implements X509TrustManager {
    596                    @Override
    597                    public void checkClientTrusted(X509Certificate[] arg0, String arg1)
    598                    throws CertificateException {
    599                            throw new CertificateException("dummyTrustManager:checkClientTrusted called");
    600                    }
    601
    602                    @Override
    603                    public void checkServerTrusted(X509Certificate[] arg0, String arg1)
    604                    throws CertificateException {
    605                            throw new CertificateException("dummyTrustManagercheckServerTrusted called");
    606                    }
    607
    608                    @Override
    609                    public X509Certificate[] getAcceptedIssuers() {
    610                            Log.d(TAG, "dummyTrustManager:getAcceptedIssuers");
    611                            return null;
    612                    }
    613            }
    614
    615
    616    }