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    }