    001    package com.skype.ipc;
    002
    003    import java.io.FileOutputStream;
    004    import java.io.IOException;
    005    import java.io.InputStream;
    006    import java.io.OutputStream;
    007    import java.lang.reflect.Method;
    008    import java.net.Socket;
    009    import java.nio.ByteBuffer;
    010    import java.security.KeyManagementException;
    011    import java.security.NoSuchAlgorithmException;
    012    import java.security.Principal;
    013    import java.security.PrivateKey;
    014    import java.security.cert.CertificateException;
    015    import java.security.cert.X509Certificate;
    016
    017    import javax.net.ssl.KeyManager;
    018    import javax.net.ssl.SSLContext;
    019    import javax.net.ssl.SSLEngine;
    020    import javax.net.ssl.SSLEngineResult;
    021    import javax.net.ssl.SSLEngineResult.HandshakeStatus;
    022    import javax.net.ssl.SSLException;
    023    import javax.net.ssl.SSLParameters;
    024    import javax.net.ssl.SSLSession;
    025    import javax.net.ssl.TrustManager;
    026    import javax.net.ssl.X509ExtendedKeyManager;
    027    import javax.net.ssl.X509TrustManager;
    028
    029    public class LoggedTlsInputOutputStream implements OutputTransporting, InputTransporting {
    030
    031            public LoggedTlsInputOutputStream(InputStream input, OutputStream output, ClientConfiguration cfg) throws IOException {
    032
    033                    mInput                  = input;
    034                    mOutput                 = output;
    035
    036                    if (cfg.generateTransportLog()) {
    037                            try {
    038                                    mInputTransportLog  = new FileOutputStream(cfg.getInputTransportLogName());
    039                                    mOutputTransportLog = new FileOutputStream(cfg.getOutputTransportLogName());
    040                            } catch (IOException e) {
    041                                    mInputTransportLog  = null;
    042                                    mOutputTransportLog = null;
    043                            }
    044                    }
    045
    046                    init(cfg);
    047            }
    048
    049            public void skipBytes(int numBytes) throws IOException {
    050                    if (mApplicationDataToRead == null)
    051                            throw new  IOException("Connection was closed");
    052
    053                    while (numBytes > mAvailable) {
    054                            numBytes  -= mAvailable;
    055                            mApplicationDataToRead.clear();
    056                            mAvailable = read(mApplicationDataToRead);
    057                    }
    058
    059                    mAvailable -= numBytes;
    060                    if (mAvailable > 0) {
    061                            mApplicationDataToRead.position(mApplicationDataToRead.position()+numBytes);
    062                    } else {
    063                            mApplicationDataToRead.clear();
    064                    }
    065            }
    066
    067            public void readBytes(byte[] dest) throws IOException {
    068                    readBytes(dest, 0, dest.length);
    069            }
    070
    071            private int read(ByteBuffer dest) throws IOException {
    072                    // assert dest.capacity() == ApplicationBufferSize
    073                    int offset = mPacketToRead.position();
    074                    int length = mPacketBufferSize - offset;
    075                    int produced = 0;
    076                    if (offset == 0) {
    077                            offset = mInput.read(mPacketToRead.array(), offset, length);
    078                            if (offset < 0) throw new IOException("Connection closed");
    079                            mPacketToRead.limit(offset);
    080                            length -= offset;
    081                    } else {
    082                            mPacketToRead.flip();
    083                    }
    084                    boolean cont = false;
    085                    do {
    086                            SSLEngineResult result = mEngine.unwrap(mPacketToRead, dest);
    087                            switch (result.getStatus()) {
    088                            case OK:
    089                                    produced += result.bytesProduced();
    090                                    cont = false;
    091                                    break;
    092                            case BUFFER_UNDERFLOW:
    093                                    produced += result.bytesProduced();
    094                                    int rd = mInput.read(mPacketToRead.array(), offset, length);
    095                                    if (rd < 0) throw new IOException("Connection closed");
    096                                    mPacketToRead.limit(offset+rd);
    097                                    length -= rd;
    098                                    offset += rd;
    099                                    break;
    100                            default:
    101                                    // assumption a PacketBufferSize can fill at most an ApplicationBufferSize, thus no overflow expected
    102                                    throw new IOException("TLS "+result.getStatus() + " error while reading");
    103                            }
    104                    } while (cont);
    105                    if (mPacketToRead.remaining() == 0) {
    106                            mPacketToRead.clear();
    107                    } else {
    108                            mPacketToRead.compact();
    109                    }
    110                    if (mInputTransportLog != null && produced > 0) {
    111                            mInputTransportLog.write(dest.array(), 0, produced);
    112                    }
    113                    return produced;
    114            }
    115
    116            public void readBytes(byte[] dest, int offset, int length) throws IOException {
    117                    if (mApplicationDataToRead == null)
    118                            throw new  IOException("Connection was closed");
    119                    if (length > mAvailable) {
    120
    121                            if (mAvailable > 0) {
    122                                    mApplicationDataToRead.get(dest, 0, mAvailable);
    123                                    length -= mAvailable;
    124                                    offset  = mAvailable;
    125                                    mApplicationDataToRead.clear();
    126                            }
    127
    128                            while (length >= mApplicationBufferSize) {
    129                                    ByteBuffer dst = ByteBuffer.wrap(dest, offset, length);
    130                                    int rd = read(dst);
    131                                    length -= rd;
    132                                    offset += rd;
    133                            }
    134
    135                            while (length > 0) {
    136                                    int rd = read(mApplicationDataToRead);
    137                                    mApplicationDataToRead.position(0);
    138                                    mApplicationDataToRead.limit(rd);
    139
    140                                    if (rd >= length) {
    141                                            mAvailable = rd;
    142                                            break;
    143                                    }
    144
    145                                    mApplicationDataToRead.get(dest, offset, rd);
    146                                    mApplicationDataToRead.clear();
    147                                    offset += rd;
    148                                    length -= rd;
    149                            }
    150
    151                    }
    152
    153                    mApplicationDataToRead.get(dest, offset, length);
    154                    mAvailable -=  length;
    155                    if (mAvailable == 0)
    156                            mApplicationDataToRead.clear();
    157    //              for (int i = 0, e = dest.length; i < e; i++)
    158    //              System.err.println( "'" + dest[i] + "'" );
    159
    160            }
    161
    162            public int readByte() throws IOException {
    163                    if (mApplicationDataToRead == null)
    164                            throw new  IOException("Connection was closed");
    165                    while (mAvailable == 0) {
    166                            mAvailable = read(mApplicationDataToRead);
    167                            mApplicationDataToRead.flip();
    168                    }
    169                    byte b = mApplicationDataToRead.get();
    170    //          System.err.println( "'" + b + "'" );
    171                    mAvailable--;
    172                    if (mAvailable == 0)
    173                            mApplicationDataToRead.clear();
    174                    return b;
    175            }
    176
    177            public void writeBytes(byte [] src) throws IOException {
    178                    if (mApplicationDataToSend == null) throw new IOException("Connection was closed");
    179                    int length = src.length;
    180                    int filling= 0;
    181                    if ((length+mApplicationDataSize) > mApplicationBufferSize) { // use some gather call?
    182                            filling = mApplicationBufferSize - mApplicationDataSize;
    183                            length     -= filling;
    184                            mApplicationDataSize = 0;
    185                            if (filling > 0) {
    186                                    mApplicationDataToSend.put(src, 0, filling);
    187                                    write(mApplicationDataToSend);
    188                            }
    189                            while (length >= mApplicationBufferSize) {
    190                                    ByteBuffer buf = ByteBuffer.wrap(src, filling, mApplicationBufferSize);
    191                                    write(buf);
    192                                    filling += mApplicationBufferSize;
    193                                    length  -= mApplicationBufferSize;
    194                            }
    195                            if (length == 0) return;
    196                    }
    197                    mApplicationDataToSend.put(src, filling, length);
    198                    mApplicationDataSize += length; // getPosition shall be the same...
    199            }
    200
    201            public void writeBytesAndFlush(byte [] src) throws IOException {
    202                    writeBytes(src);
    203                    if (mApplicationDataSize > 0) {
    204                            write(mApplicationDataToSend);
    205                            mApplicationDataSize = 0;
    206                    }
    207            }
    208
    209            public void write(ByteBuffer src) throws IOException {
    210                    src.flip();
    211                    SSLEngineResult result;
    212                    do {
    213                            try {
    214                                    result = mEngine.wrap(src, mPacketToSend);
    215                                    if (result.getStatus() != SSLEngineResult.Status.OK) throw new IOException("error when wrapping");
    216                            } catch (SSLException e) {
    217                                    throw new IOException("error when wrapping " + e.getMessage());
    218                            }
    219                            mOutput.write(mPacketToSend.array(), 0, result.bytesProduced());
    220                            mPacketToSend.clear();
    221                    } while (src.hasRemaining());
    222                    src.clear();
    223            }
    224
    225            public void writeByte(int value) throws IOException {
    226                    if (mApplicationDataToSend == null) throw new IOException("Connection was closed");
    227                    if (mApplicationDataSize == mApplicationBufferSize) {
    228                            write(mApplicationDataToSend);
    229                            mApplicationDataSize = 0;
    230                    }
    231                    mApplicationDataToSend.put((byte) value);
    232                    mApplicationDataSize++; // getPosition shall be the same...
    233            }
    234
    235            public void writeByteAndFlush(int value) throws IOException {
    236                    if (mApplicationDataToSend == null) throw new IOException("Connection was closed");
    237                    if (mApplicationDataSize == mApplicationBufferSize) {
    238                            write(mApplicationDataToSend);
    239                    }
    240                    mApplicationDataToSend.put((byte) value);
    241                    write(mApplicationDataToSend); // hopefully works with 1 byte... else one may fill with useless 'zzz'
    242                    mApplicationDataSize = 0;
    243            }
    244
    245            private SSLEngine          mEngine;
    246            private int                mApplicationDataSize;
    247            private int                mPacketBufferSize;
    248            private int                mApplicationBufferSize;
    249            private int                mAvailable;
    250            private ByteBuffer         mPacketToSend;
    251            private ByteBuffer         mApplicationDataToSend;
    252            private OutputStream       mOutput;
    253            private FileOutputStream   mOutputTransportLog;
    254            private ByteBuffer         mPacketToRead;
    255            private ByteBuffer         mApplicationDataToRead;
    256            private InputStream        mInput;
    257            private FileOutputStream   mInputTransportLog;
    258
    259            public void init(ClientConfiguration cfg) throws IOException
    260            {
    261                    try {
    262                            context = SSLContext.getInstance("TLS");
    263                    } catch (NoSuchAlgorithmException e) {
    264                            throw new IOException("TLSServerTransport:IOException:" + e.getMessage() );
    265                    }
    266
    267                    dummyTrustManager trustManager = new dummyTrustManager();
    268                    dummyKeyManager   keyManager   = new dummyKeyManager(cfg.getX509Certificate(),cfg.getPrivateKey());
    269
    270                    KeyManager[] km =   { keyManager   };
    271                    TrustManager[] tm = { trustManager };
    272
    273                    try {
    274                            context.init(km, tm, null);
    275                    } catch (KeyManagementException e) {
    276                            throw new IOException("TLSServerTransport:KeyManagementException:" + e.getMessage() );
    277                    }
    278
    279                    SSLEngine engine = context.createSSLEngine();
    280                    engine.setUseClientMode(false);
    281                    engine.setNeedClientAuth(false);
    282                    engine.setWantClientAuth(false);
    283
    284                    mEngine                 = engine;
    285                    SSLSession s            = engine.getSession();
    286                    mApplicationBufferSize  = s.getApplicationBufferSize();
    287                    mPacketBufferSize       = s.getPacketBufferSize();
    288                    mPacketToSend           = ByteBuffer.allocate(mPacketBufferSize);
    289                    mPacketToRead           = ByteBuffer.allocate(mPacketBufferSize);
    290                    mApplicationDataToSend  = ByteBuffer.allocate(mApplicationBufferSize); // may rather use the configuration one
    291                    mApplicationDataToRead  = ByteBuffer.allocate(mApplicationBufferSize);
    292
    293                    try {
    294                            @SuppressWarnings("rawtypes")
    295                            Class c = Class.forName("javax.net.ssl.SSLEngine");
    296                            for (Method m : c.getDeclaredMethods() ) {
    297                                    if (m.getName() == "getSSLParameters") { //this is not supported on Android < 2.3
    298                                            SSLParameters params = engine.getSSLParameters();
    299                                            params.setCipherSuites(new String [] {
    300                                                    "TLS_RSA_WITH_AES_128_CBC_SHA"
    301                                            });
    302                                            engine.setSSLParameters(params);
    303                                            }
    304                                    }
    305                            }
    306                    catch (ClassNotFoundException e) { //shouldnt ever happen
    307                        throw new IOException("TLSServerTransport:init_failed:" + e.getMessage(), e);
    308                    }
    309
    310                    try {
    311                            do_handshake();
    312                    }
    313                    catch (SSLException e) {
    314                        throw new IOException("TLSServerTransport:handshake_failed:" + e.getMessage(), e);
    315                    }
    316                    catch (IOException e) {
    317                        throw new IOException("TLSServerTransport:handshake_failed:" + e.getMessage(), e);
    318                    }
    319            }
    320
    321            void do_handshake() throws SSLException, IOException
    322            {
    323
    324                    SSLEngine engine = mEngine;
    325                    HandshakeStatus result =  HandshakeStatus.NEED_UNWRAP;
    326                    while(true) {
    327                        switch(result) {
    328                        case FINISHED:
    329                            return;
    330                        case NEED_TASK:
    331                            Runnable task;
    332                            while((task=engine.getDelegatedTask()) != null) {
    333                                task.run();
    334                            }
    335                            break;
    336                        case NEED_UNWRAP: {
    337                            mAvailable = read(mApplicationDataToRead); // shall be 0...
    338                            break;
    339                        }
    340                        case NEED_WRAP: {
    341                            write(mApplicationDataToSend); // shall be empty...
    342                            break;
    343                        }
    344                        case NOT_HANDSHAKING:
    345                            return;
    346                        }
    347                        result = engine.getHandshakeStatus();
    348
    349            }
    350            }
    351
    352            public void close() throws IOException {
    353                    mPacketToSend           = null;
    354                    mPacketToRead           = null;
    355                    mApplicationDataToSend  = null; // may rather use the configuration one
    356                    mApplicationDataToRead  = null;
    357                    try {
    358                            if (mInputTransportLog != null)
    359                                    mInputTransportLog.close();
    360                    } catch (IOException e) {
    361                    }
    362                    mInputTransportLog = null;
    363                    try {
    364                            if (mOutputTransportLog != null)
    365                                    mOutputTransportLog.close();
    366                    } catch (IOException e) {
    367                    }
    368                    mOutputTransportLog = null;
    369                    mInput.close();
    370                    mOutput.close();
    371            }
    372
    373            private SSLEngine engine = null;
    374            private SSLContext context = null;
    375
    376            public class dummyKeyManager extends X509ExtendedKeyManager {
    377                    X509Certificate cert;
    378                    PrivateKey privKey;
    379
    380                    public dummyKeyManager(X509Certificate _cert,PrivateKey _privKey) {
    381                            privKey = _privKey;
    382                            cert = _cert;
    383                    }
    384
    385                    public String chooseClientAlias(String[] arg0, Principal[] arg1, Socket arg2) {
    386                            return null;
    387                    }
    388
    389                    public String chooseEngineServerAlias(String keyType, Principal[] issuers, SSLEngine engine) {
    390                            if (keyType == "RSA") {
    391                                    return "dummyServer";
    392                            }
    393                            return null;
    394                    }
    395
    396                    public String chooseServerAlias(String keyType, Principal[] issuers, Socket arg2) {
    397                            return null;
    398                    }
    399
    400                    public X509Certificate[] getCertificateChain(String arg0) {
    401                            return new X509Certificate[] { cert };
    402                    }
    403
    404                    public String[] getClientAliases(String arg0, Principal[] arg1) {
    405                            return null;
    406                    }
    407
    408                    public PrivateKey getPrivateKey(String arg0) {
    409                            return privKey;
    410                    }
    411
    412                    public String[] getServerAliases(String arg0, Principal[] arg1) {
    413                            return null;
    414                    }
    415            }
    416
    417            /*
    418             * This class should never be called
    419             * */
    420            public class dummyTrustManager implements X509TrustManager {
    421                    public void checkClientTrusted(X509Certificate[] arg0, String arg1)
    422                    throws CertificateException {
    423                            throw new CertificateException("dummyTrustManager:checkClientTrusted called");
    424                    }
    425
    426                    public void checkServerTrusted(X509Certificate[] arg0, String arg1)
    427                    throws CertificateException {
    428                            throw new CertificateException("dummyTrustManagercheckServerTrusted called");
    429                    }
    430
    431                    public X509Certificate[] getAcceptedIssuers() {
    432                            return null;
    433                    }
    434            }
    435
    436    }