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 }