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 }