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.ByteArrayInputStream;
025 import java.io.File;
026 import java.io.FileNotFoundException;
027 import java.io.FileOutputStream;
028 import java.io.IOException;
029 import java.net.Socket;
030 import java.nio.ByteBuffer;
031 import java.security.KeyManagementException;
032 import java.security.NoSuchAlgorithmException;
033 import java.security.Principal;
034 import java.security.PrivateKey;
035 import java.security.cert.CertificateEncodingException;
036 import java.security.cert.CertificateException;
037 import java.security.cert.X509Certificate;
038 import java.util.LinkedList;
039 import java.lang.reflect.Method;
040
041 import javax.net.ssl.KeyManager;
042 import javax.net.ssl.SSLContext;
043 import javax.net.ssl.SSLEngine;
044 import javax.net.ssl.SSLEngineResult;
045 import javax.net.ssl.SSLEngineResult.HandshakeStatus;
046 import javax.net.ssl.SSLEngineResult.Status;
047 import javax.net.ssl.SSLException;
048 import javax.net.ssl.SSLParameters;
049 import javax.net.ssl.SSLSession;
050 import javax.net.ssl.TrustManager;
051 import javax.net.ssl.X509ExtendedKeyManager;
052 import javax.net.ssl.X509TrustManager;
053 import java.util.Arrays;
054 import com.skype.util.Log;
055
056 /**
057 * @author kcjones
058 *
059 */
060 public class TLSServerTransport implements Transport
061 {
062 private static final String TAG = "TLSServerTransport";
063 private static int MAXBUF;
064
065 private boolean connected = false;
066 private Transport transport = null;
067 private SSLEngine engine = null;
068 private SSLContext context = null;
069 private FileOutputStream logFileOut = null;
070 private FileOutputStream logFileIn = null;
071 private boolean isLogging = false;
072
073 private X509ExtendedKeyManager keyManager = null;
074 private TrustManager trustManager = null;
075
076 private LinkedList<byte[]> decrypted_chunks = new LinkedList<byte[]>();
077 private ByteArrayInputStream decryptedStream = new ByteArrayInputStream(new byte[0]);
078 private ByteBuffer raw_source = null;
079
080 public TLSServerTransport(Transport t, X509Certificate cert, PrivateKey privKey)
081 {
082 transport = t;
083
084 trustManager = new dummyTrustManager();
085 keyManager = new dummyKeyManager(cert,privKey);
086 decrypted_chunks.clear();
087 }
088
089 protected void finalize() throws Throwable
090 {
091 super.finalize();
092 if (isLogging) {
093 logFileOut.flush();
094 logFileOut.close();
095 logFileIn.flush();
096 logFileIn.close();
097 }
098 }
099
100 @Override
101 public void startLogging(String logFileName)
102 {
103 assert(!isLogging);
104
105 if (logFileName != null && ! logFileName.isEmpty()) {
106 try {
107 logFileOut = new FileOutputStream(new File(logFileName + ".out.bin"), false);
108 logFileIn = new FileOutputStream(new File(logFileName + ".in.bin"), false);
109 isLogging = true;
110 Log.d(TAG, "Transport logging started");
111 }
112 catch (FileNotFoundException e) {
113 Log.e(TAG, "Unable to open transport log files using:" + logFileName);
114 }
115 }
116 }
117
118 void do_handshake() throws SSLException, IOException
119 {
120 byte leftover_bytes[] = new byte[]{};
121
122 Log.d(TAG, "do_handshake()");
123 SSLEngineResult engineResult = null;
124 HandshakeStatus result = HandshakeStatus.NEED_UNWRAP;
125 while(true) {
126 switch(result) {
127 case FINISHED:
128 Log.d(TAG, result.toString());
129 return;
130 case NEED_TASK:
131 Log.d(TAG, result.toString());
132 Runnable task;
133 while((task=engine.getDelegatedTask()) != null) {
134 task.run();
135 }
136 break;
137 case NEED_UNWRAP: {
138
139 byte [] msg = new byte [16384];
140 int idx = 0;
141 Log.d(TAG, result.toString());
142
143 if (leftover_bytes.length == 0) {
144 try {
145 transport.peek();
146 while(transport.hasMore() ) {
147 msg[idx++] = (byte) transport.read();
148 }
149 }
150 catch (IOException e) {
151 Log.e(TAG, "Read exception:" + e.getMessage() + ":" + e.getCause());
152 throw e;
153 }
154 }
155 else {
156 idx = leftover_bytes.length;
157 System.arraycopy(leftover_bytes, 0, msg, 0, idx);
158 leftover_bytes = new byte[]{};
159 }
160
161 ByteBuffer src0 = ByteBuffer.wrap(msg);
162 ByteBuffer dst0 = ByteBuffer.allocate(16384);
163 engineResult = engine.unwrap(src0, dst0);
164 result = engineResult.getHandshakeStatus();
165 int bytesConsumed = engineResult.bytesConsumed();
166
167 int sum = 0; //check if we had trailing zeroes
168 while(bytesConsumed < idx)
169 sum |= msg[bytesConsumed++];
170
171 int bytesLeftOver = idx - engineResult.bytesConsumed();
172 if (sum != 0 && bytesLeftOver > 0 ) { //more than one message, keep a copy
173 leftover_bytes = new byte[bytesLeftOver];
174 System.arraycopy(msg, engineResult.bytesConsumed(),
175 leftover_bytes, 0, bytesLeftOver);
176 }
177 break;
178 }
179 case NEED_WRAP: {
180 Log.d(TAG, result.toString());
181 ByteBuffer EMPTY = ByteBuffer.allocate(0);
182 ByteBuffer dst1 = ByteBuffer.allocate(65536);
183 engineResult = engine.wrap(EMPTY, dst1);
184 byte [] bytes2send = new byte[engineResult.bytesProduced()];
185 dst1.rewind();
186 dst1.get(bytes2send);
187 transport.write(bytes2send.length , bytes2send);
188 break;
189 }
190 case NOT_HANDSHAKING:
191 Log.d(TAG, "discarding " + leftover_bytes.length + " bytes");
192 if (leftover_bytes.length > 0) {
193 ByteBuffer s = ByteBuffer.wrap(leftover_bytes);
194 Log.hexdump(s.array());
195 leftover_bytes = new byte[] {};
196 }
197 Log.d(TAG, result.toString());
198 return;
199 }
200 result = engine.getHandshakeStatus();
201
202 }
203 }
204
205 void checkConnected() throws IOException
206 {
207 if (!connected)
208 throw new IOException("TLSServerTransport not connected!");
209 }
210
211 @Override
212 public boolean connect() throws IOException
213 {
214 Log.d(TAG, "connect()");
215
216 if (!transport.connect())
217 return false;
218
219 try {
220 context = SSLContext.getInstance("TLS");
221 } catch (NoSuchAlgorithmException e) {
222 throw new IOException("TLSServerTransport:IOException:" + e.getMessage() );
223 }
224 KeyManager [] km = { keyManager };
225 TrustManager [] tm = { trustManager };
226 try {
227 context.init(km, tm, null);
228 } catch (KeyManagementException e) {
229 throw new IOException("TLSServerTransport:KeyManagementException:" + e.getMessage() );
230 }
231 engine = context.createSSLEngine();
232 engine.setUseClientMode(false);
233 engine.setNeedClientAuth(false);
234 engine.setWantClientAuth(false);
235
236 try {
237 @SuppressWarnings("rawtypes")
238 Class c = Class.forName("javax.net.ssl.SSLEngine");
239 for (Method m : c.getDeclaredMethods() ) {
240 if (m.getName() == "getSSLParameters") { //this is not supported on Android < 2.3
241 SSLParameters params = engine.getSSLParameters();
242 params.setCipherSuites(new String [] {
243 "TLS_RSA_WITH_AES_128_CBC_SHA"
244 });
245 engine.setSSLParameters(params);
246 }
247 }
248 }
249 catch (ClassNotFoundException e) { //shouldnt ever happen
250 }
251
252 try {
253 do_handshake();
254 }
255 catch (SSLException e) {
256 throw new IOException("TLSServerTransport:handshake_failed:" + e.getMessage(), e);
257 }
258 catch (IOException e) {
259 throw new IOException("TLSServerTransport:handshake_failed:" + e.getMessage(), e);
260 }
261 connected = true;
262
263 X509Certificate cert = keyManager.getCertificateChain("dummyServer")[0];
264 byte[] certBytes = null;
265 try {
266 certBytes = cert.getEncoded();
267 } catch (CertificateEncodingException e) {
268 e.printStackTrace();
269 }
270 String lenStr = String.format("%08X", certBytes.length);
271 byte [] lenBytes = lenStr.getBytes();
272 write(lenBytes.length,lenBytes);
273 write(certBytes.length,certBytes);
274
275 return true;
276 }
277
278 @Override
279 public void disconnect() throws IOException
280 {
281 checkConnected();
282
283 transport.disconnect();
284 }
285
286 @Override
287 public boolean hasMore() throws IOException
288 {
289 // never used, according to KCJ
290 assert(false);
291
292 return false;
293 }
294
295 @Override
296 public boolean isConnected() throws IOException
297 {
298 return transport.isConnected();
299 }
300
301 @Override
302 public int peek() throws IOException
303 {
304 if (decryptedStream.available() == 0) {
305 // this will block until bytes arrive, but we return a decrypted byte after this returns
306 transport.peek();
307 readMore();
308 }
309
310 decryptedStream.mark(1);
311 int b = decryptedStream.read();
312 decryptedStream.reset();
313 return b;
314 }
315
316 @Override
317 public int read() throws IOException
318 {
319 if (decryptedStream.available() > 0)
320 return decryptedStream.read();
321
322 byte b[] = new byte[]{'0'};
323 read(1,b);
324
325 return b[0];
326 }
327
328 @Override
329 public synchronized int read(int numBytes, byte[] bytes) throws IOException
330 {
331 checkConnected();
332
333 int len, offset = 0, remaining = numBytes;
334 while ((len = decryptedStream.available()) < remaining) {
335 if (len > 0) {
336 decryptedStream.read(bytes, offset, len);
337 offset += len;
338 remaining -= len;
339 }
340
341 readMore();
342 }
343
344 decryptedStream.read(bytes, offset, remaining);
345 return numBytes;
346 }
347
348 @Override
349 public int read(int numBytes, byte[] bytes, boolean needNumBytes) throws IOException
350 {
351 int bytesRead = read(numBytes, bytes);
352 return bytesRead;
353 }
354
355 /**
356 * drain all input from transport stream and decrypt using engine.
357 *
358 * @param raw_bytes - buffer to accept unwrapped bytes read from socket
359 * @throws IOException - when read buffer is too small, or other socket read exceptions thrown
360 */
361 protected void readMore() throws IOException
362 {
363 assert(decryptedStream.available() == 0);
364 if (decrypted_chunks.isEmpty()) {
365 readChunks();
366 }
367 // either we find soemthing to read, or throw an exception
368 assert( ! decrypted_chunks.isEmpty());
369 byte [] chunk = decrypted_chunks.removeFirst();
370 decryptedStream = new ByteArrayInputStream(chunk);
371 }
372
373 protected void readChunks() throws IOException
374 {
375 // allocate byte buffers to match session parameters exactly
376 if (raw_source == null) {
377 SSLSession s = engine.getSession();
378 MAXBUF = s.getPacketBufferSize();
379 if (s.getApplicationBufferSize() > MAXBUF) {
380 MAXBUF = s.getApplicationBufferSize();
381 }
382 Log.d(TAG, "SSLSession packet size:" + MAXBUF);
383 raw_source = ByteBuffer.allocate(MAXBUF);
384
385 }
386
387 int pos = raw_source.position();
388 // System.err.println("reading "+pos);
389 do {
390 raw_source.put((byte)transport.read());
391 } while( transport.hasMore() && raw_source.position() < MAXBUF );
392
393 if (pos == raw_source.position()) {
394 // System.err.println("nothing read");
395 Log.d(TAG, "Nothing read.");
396 return;
397 }
398 // System.err.println("read "+raw_source.position());
399
400 ByteBuffer dst = ByteBuffer.allocate(MAXBUF);
401 boolean unwrap_ok = false;
402 int loop = 0;
403 do {
404 SSLEngineResult engineResult;
405 try {
406 raw_source.flip();
407 // System.err.println("unwrapping "+raw_source.remaining());
408 engineResult = engine.unwrap(raw_source, dst);
409 raw_source.compact();
410 // System.err.println("unwrapped "+raw_source.position()+" "+raw_source.limit());
411 } catch (SSLException e) {
412 String msg = "unwrap failed at bytes "+engine.getSession().getCipherSuite();
413 System.err.println(msg);
414 Log.d(TAG, msg);
415 throw e;
416 }
417
418 SSLEngineResult.Status unwrap_status = engineResult.getStatus();
419 //Log.d(TAG, "unwrap status:" + unwrap_status);
420 unwrap_ok = unwrap_status == SSLEngineResult.Status.OK;
421 if (unwrap_ok || unwrap_status == SSLEngineResult.Status.BUFFER_UNDERFLOW)
422 {
423 int l = engineResult.bytesProduced();
424 //Log.d(TAG, "decrypted length:" + l);
425 if (l > 0 || loop == 0) {
426 loop++;
427 byte [] chunk = new byte[l];
428 dst.rewind();
429 dst.get(chunk, 0, l);
430 dst.clear();
431 if (isLogging)
432 logFileIn.write(chunk);
433
434 decrypted_chunks.addLast(chunk);
435 }
436 // if (!unwrap_ok)
437 // System.err.println("underflow, adding chunk with "+l+" bytes, remains "+raw_source.position());
438 // else
439 // System.err.println("ok, adding chunk with "+l+" bytes, remains "+raw_source.position());
440 }
441 else {
442 throw new IOException("Unrecoverable TLS error: " + unwrap_status);
443 }
444 } while (unwrap_ok && raw_source.position() > 0);
445 // System.err.println("end reading");
446 }
447
448 private void dumpSourceBuffer(String tag)
449 {
450 int pos = raw_source.position();
451 int lim =raw_source.limit();
452 Log.e(TAG, "---" + tag + " position:" + pos + " limit:" + lim);
453
454 if (raw_source.hasArray() && pos < lim) {
455 byte [] src = raw_source.array();
456 byte [] section = new byte[lim - pos];
457 System.arraycopy(src, pos, section, 0, lim - pos);
458 Log.hexdump(section);
459 }
460 }
461
462 @Override
463 public boolean write(byte b) throws IOException
464 {
465 Log.e(TAG, "FAIL! write(" + b + ")");
466 throw new IOException("Invalid use of TLS transport");
467 }
468
469 @Override
470 public boolean write(int numBytes, byte[] bytes)
471 throws IOException
472 {
473 checkConnected();
474
475 if (isLogging) {
476 logFileOut.write(bytes, 0, numBytes);
477 }
478
479 SSLSession s = engine.getSession();
480 int begin = 0;
481 int packetBufferSize = s.getPacketBufferSize();
482 ByteBuffer dst = ByteBuffer.allocate(packetBufferSize);
483 ByteBuffer src = ByteBuffer.wrap(bytes);
484 while (numBytes > 0) {
485 SSLEngineResult engineResult = engine.wrap(src, dst);
486 if (engineResult.getStatus() != SSLEngineResult.Status.OK) {
487 throw new IOException("error when wrapping");
488 }
489 numBytes -= engineResult.bytesConsumed();
490 // System.err.println("write, remains "+numBytes);
491 transport.write(dst.position(), dst.array());
492 dst.clear();
493 }
494
495 return false;
496 }
497
498 public class dummyKeyManager extends X509ExtendedKeyManager {
499 X509Certificate cert;
500 PrivateKey privKey;
501
502 public dummyKeyManager(X509Certificate _cert,PrivateKey _privKey) {
503 privKey = _privKey;
504 cert = _cert;
505 }
506
507 @Override
508 public String chooseClientAlias(String[] arg0, Principal[] arg1, Socket arg2) {
509 Log.d(TAG, "chooseClientAlias");
510 return null;
511 }
512
513 public String chooseEngineServerAlias(String keyType, Principal[] issuers, SSLEngine engine) {
514 Log.d(TAG, "chooseEngineServerAlias " + keyType);
515 if (keyType == "RSA") {
516 return "dummyServer";
517 }
518 return null;
519 }
520
521 @Override
522 public String chooseServerAlias(String keyType, Principal[] issuers, Socket arg2) {
523 Log.d(TAG, "chooseServerAlias");
524 return null;
525 }
526
527 @Override
528 public X509Certificate[] getCertificateChain(String arg0) {
529 Log.d(TAG, "getCertificateChain");
530 return new X509Certificate[] { cert };
531 }
532
533 @Override
534 public String[] getClientAliases(String arg0, Principal[] arg1) {
535 Log.d(TAG, "getClientAliases");
536 return null;
537 }
538
539 @Override
540 public PrivateKey getPrivateKey(String arg0) {
541 Log.d(TAG, "getPrivateKey");
542 return privKey;
543 }
544
545 @Override
546 public String[] getServerAliases(String arg0, Principal[] arg1) {
547 Log.d(TAG, "getServerAliases");
548 return null;
549 }
550 }
551
552 /*
553 * This class should never be called
554 * */
555 public class dummyTrustManager implements X509TrustManager {
556 @Override
557 public void checkClientTrusted(X509Certificate[] arg0, String arg1)
558 throws CertificateException {
559 throw new CertificateException("dummyTrustManager:checkClientTrusted called");
560 }
561
562 @Override
563 public void checkServerTrusted(X509Certificate[] arg0, String arg1)
564 throws CertificateException {
565 throw new CertificateException("dummyTrustManagercheckServerTrusted called");
566 }
567
568 @Override
569 public X509Certificate[] getAcceptedIssuers() {
570 Log.d(TAG, "dummyTrustManager:getAcceptedIssuers");
571 return null;
572 }
573 }
574 }