Source code: com/ciphercore/Client.java
1 /* $Id: Client.java,v 1.9 2001/04/02 08:42:50 cvsbob Exp $ */
2
3 /*
4 * Client.java, core class for use by ciphercore clients.
5 * Copyright (C) 2001 Robert Bushman.
6 *
7 * I reserve the right to release this program under seperate license.
8 * If you require a special license grant contact Robert Bushman.
9 *
10 * This program is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU General Public License
12 * as published by the Free Software Foundation; either version 2
13 * of the License, or (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
23 * 02111-1307, USA.
24 */
25
26 package com.ciphercore;
27
28 import java.io.BufferedInputStream;
29 import java.io.DataInputStream;
30 import java.io.DataOutputStream;
31 import java.io.FileInputStream;
32 import java.io.InputStream;
33 import java.io.IOException;
34 import java.io.OutputStream;
35 import java.math.BigInteger;
36 import java.net.Socket;
37 import java.security.InvalidAlgorithmParameterException;
38 import java.security.InvalidKeyException;
39 import java.security.Provider;
40 import java.security.Security;
41 import java.security.spec.InvalidKeySpecException;
42 import java.util.Properties;
43 import javax.crypto.Cipher;
44 import javax.crypto.CipherInputStream;
45 import javax.crypto.CipherOutputStream;
46
47 import com.traxel.crypto.CipherPartner;
48 import com.traxel.crypto.CryptoUtil;
49 import com.traxel.crypto.Sha1Util;
50 import com.traxel.util.ClassUtil;
51
52 /**
53 * Client is the class that establishes the connection
54 * with the Child. It handles all the crypto negotiation
55 * and prepares a pair of streams for communicating with
56 * your child.
57 * <br><br>
58 * <b>Typical Usage:</b><pre><tt>
59 * import com.ciphercore.Client;
60 * import com.ciphercore.Server;
61 *
62 * public class MyClient {
63 *
64 * public static void main( String[] args ) {
65 *
66 * // Instantiate a client, tell it the child type, host and port.
67 * Client client = new Client( "mypackage.MyChild",
68 * "localhost",
69 * Server.DEFAULT_PORT );
70 *
71 * // Tell the client to establish the connection to the child.
72 * client.connect();
73 *
74 * try {
75 *
76 * // Get the streams that connect to the Child.
77 * InputStream in = client.getBaseInputStream();
78 * OutputStream out = client.getBaseOutputStream();
79 *
80 * // Do whatever you do.
81 * while( ! finished ) {
82 * ... Communicate With MyChild ...
83 * }
84 *
85 * // Not vital (Client closes them), but good habit.
86 * out.close();
87 * in.close();
88 *
89 * } catch( IOException e ) {
90 * ... handle the exception ...
91 * } finally {
92 * // Release the network connection.
93 * client.close();
94 * }
95 * }
96 * }
97 * </tt></pre>
98 */
99 public class Client {
100
101 // -------------------------------------------------------
102 // CONSTANTS
103 // -------------------------------------------------------
104
105 /** If no host is specified, connect to "localhost". */
106 public static final String DEFAULT_HOST_NAME = "localhost";
107
108 // -------------------------------------------------------
109 // CONSTRUCTORS
110 // -------------------------------------------------------
111
112 /**
113 * Creates a client using the default host and port.
114 *
115 * @param childClassName Specifies the class of the child that
116 * this client communicates with.
117 */
118 public Client( String childClassName ) {
119 this( childClassName, DEFAULT_HOST_NAME, Server.DEFAULT_PORT );
120 }
121
122 /**
123 * Creates a client using the specified host and port.
124 *
125 * @param childClassName Specifies the class of the child that
126 * this client communicates with.
127 * @param hostName Specifies the hostname where the
128 * CipherCore server is located.
129 * @param port Specifiest the port that the CipherCore
130 * server is listening on.
131 */
132 public Client( String childClassName,
133 String hostName,
134 int port ) {
135 locateFiles();
136 readBasicCfg();
137 readAdvancedCfg();
138 readServerHashes();
139 setChildClassName( childClassName );
140 setHostName( hostName );
141 setPort( port );
142 CryptoUtil.initializeProvider( getProviderName() );
143 setCipherSelf( new CipherPartner( "Client", DHStock.PARAMS ) );
144 }
145
146 // ----------------------------------------------------------
147 // PUBLIC API
148 // ----------------------------------------------------------
149
150 /**
151 * Establishes the connection as specified in the constructor.
152 * This is where host switching would be implemented
153 * if this system is intended to be capable of talking
154 * to a balanced cluster.
155 *
156 * @exception IOException If the connection fails.
157 */
158 public void connect()
159 throws IOException,
160 ServerKeyException,
161 UserPasswordException {
162
163 Socket socket;
164 InputStream rawIn;
165 OutputStream rawOut;
166 BufferedInputStream buffIn;
167 DataInputStream in;
168 DataOutputStream out;
169
170 socket = new Socket( getHostName(), getPort() );
171 rawIn = socket.getInputStream();
172 rawOut = socket.getOutputStream();
173 buffIn = new BufferedInputStream( rawIn );
174 in = new DataInputStream( buffIn );
175 out = new DataOutputStream( rawOut );
176
177 System.out.println( "Requesting Child Type: " + getChildClassName() );
178
179 out.writeInt( getChildClassName().length() );
180 out.write( getChildClassName().getBytes() );
181
182 // FOR CLUSTER BALANCING ADD SOMETHING LIKE:
183 // in.readFully( newHostNameBytes );
184
185 try {
186 System.out.println( "Negotiating Shared Secret" );
187
188 int publicEncodedLength = in.readInt();
189 byte[] serverPublicEncoded = new byte[ publicEncodedLength ];
190 in.readFully( serverPublicEncoded );
191
192 verifyServer( serverPublicEncoded );
193
194 getCipherSelf().initFromPartner( serverPublicEncoded );
195
196 out.writeInt( getPublicEncoded().length );
197 out.write( getPublicEncoded() );
198 out.writeInt( getIv().length );
199 out.write( getIv() );
200
201 CipherInputStream cipherIn =
202 new CipherInputStream( rawIn, getDecryptCipher() );
203 CipherOutputStream cipherOut =
204 new CipherOutputStream( rawOut, getEncryptCipher() );
205
206 setBaseInputStream( cipherIn );
207 setBaseOutputStream( cipherOut );
208
209 sendPassword();
210
211 } catch( ServerKeyException e ) {
212 System.out.println( "Caught ServerKeyException" );
213 try {
214 out.writeInt( -1 );
215 } catch( Exception f ) {
216 e.printStackTrace();
217 System.out.println( "out.writeInt( -1 ) exception" );
218 // Not much we can do about this.
219 }
220 throw e;
221 } catch( InvalidKeyException e ) {
222 e.printStackTrace();
223 System.exit( 1 );
224 } catch( InvalidAlgorithmParameterException e ) {
225 e.printStackTrace();
226 System.exit( 1 );
227 } catch( InvalidKeySpecException e ) {
228 e.printStackTrace();
229 System.exit( 1 );
230 }
231 }
232
233 protected void sendPassword()
234 throws UserPasswordException,
235 IOException {
236 DataInputStream in;
237 DataOutputStream out;
238 int serverResponse;
239
240 in = new DataInputStream( getBaseInputStream() );
241 out = new DataOutputStream( getBaseOutputStream() );
242
243 out.writeInt( getUserName().getBytes().length );
244 out.write( getUserName().getBytes() );
245 out.writeInt( getPassword().getBytes().length );
246 out.write( getPassword().getBytes() );
247 serverResponse = in.readInt();
248
249 if( serverResponse == CipherCoreProtocol.PASSWORD_NOT_REGISTERED ) {
250 throw new UserPasswordException
251 ( UserPasswordException.TYPE_USER_UNKNOWN );
252 }
253 if( serverResponse == CipherCoreProtocol.PASSWORD_INCORRECT ) {
254 throw new UserPasswordException
255 ( UserPasswordException.TYPE_PASSWORD_INCORRECT );
256 }
257 }
258
259 protected void verifyServer( byte[] serverPublicEncoded )
260 throws ServerKeyException {
261
262 String serverHash;
263 ServerKeyException digestException;
264
265 serverHash = getServerHash( getHostName() );
266 if( serverHash == null ) {
267 digestException = new ServerKeyException
268 ( ServerKeyException.TYPE_HOST_UNKNOWN );
269 digestException.setHostName( getHostName() );
270 digestException.setCurrentDigest( serverHash );
271 digestException.setNewDigest
272 ( Sha1Util.getSha1( serverPublicEncoded ) );
273 throw digestException;
274 } else {
275 System.out.println( "Found hash for " + getHostName() + "." );
276 }
277
278 try {
279 boolean serverVerified =
280 Sha1Util.verify( serverPublicEncoded, serverHash );
281 if( ! serverVerified ) {
282 digestException = new ServerKeyException
283 ( ServerKeyException.TYPE_KEY_VERIFICATION_FAILURE );
284 digestException.setHostName( getHostName() );
285 digestException.setCurrentDigest( serverHash );
286 digestException.setNewDigest
287 ( Sha1Util.getSha1( serverPublicEncoded ) );
288 throw digestException;
289 } else {
290 System.out.println( "Server key matches hash." );
291 }
292 } catch( NumberFormatException e ) {
293 digestException = new ServerKeyException
294 ( ServerKeyException.TYPE_DIGEST_CORRUPT );
295 digestException.setHostName( getHostName() );
296 digestException.setCurrentDigest( (BigInteger)null );
297 digestException.setNewDigest
298 ( Sha1Util.getSha1( serverPublicEncoded ) );
299 throw digestException;
300 }
301 }
302
303 /** Closes the IO Streams and the socket. */
304 public void close() {
305 try {
306 getBaseOutputStream().close();
307 getBaseInputStream().close();
308 } catch( Exception e ) {
309 } finally {
310 try {
311 getSocket().close();
312 } catch( Exception e ) {}
313 }
314 }
315
316 // -------------------------------------------------------------
317 // INTERNAL API
318 // -------------------------------------------------------------
319
320 protected void locateFiles() {
321 String byteCodePath = ClassUtil.pathToBytecode( this );
322 String configPath = null;
323 String jarLocation = "/lib/";
324 String classLocation = "/src/com/ciphercore/Client.class";
325 if( byteCodePath.indexOf( ".jar" ) > -1 ) {
326 configPath = byteCodePath.substring
327 ( 5, byteCodePath.lastIndexOf( jarLocation ) );
328 } else {
329 configPath = byteCodePath.substring
330 ( 5, byteCodePath.lastIndexOf( classLocation ) );
331 }
332 configPath = configPath + "/config/client";
333
334 setBasicCfgLocation( configPath + "/basic.cfg" );
335 setAdvancedCfgLocation( configPath + "/advanced.cfg" );
336 setServerHashesLocation( configPath + "/server.hashes" );
337 }
338
339 protected void readServerHashes() {
340 FileInputStream fileIn;
341 Properties serverHashes;
342
343 serverHashes = new Properties();
344 try {
345 fileIn = new FileInputStream( getServerHashesLocation() );
346 serverHashes.load( fileIn );
347 } catch( Exception e ) {
348 e.printStackTrace();
349 System.err.println( "Failed to access server hashes file." );
350 System.err.println( " Location: " + getServerHashesLocation() );
351 System.exit( 1 );
352 }
353 setServerHashes( serverHashes );
354 }
355
356 protected void readBasicCfg() {
357 FileInputStream fileIn;
358 Properties properties;
359
360 properties = new Properties();
361 try {
362 fileIn = new FileInputStream( getBasicCfgLocation() );
363 properties.load( fileIn );
364 } catch( Exception e ) {
365 e.printStackTrace();
366 System.err.println( "Failed to access basic config file." );
367 System.err.println( " Location: " + getBasicCfgLocation() );
368 System.exit( 1 );
369 }
370 }
371
372 protected void readAdvancedCfg() {
373 FileInputStream fileIn;
374 Properties properties;
375
376 properties = new Properties();
377 try {
378 fileIn = new FileInputStream( getAdvancedCfgLocation() );
379 properties.load( fileIn );
380 } catch( Exception e ) {
381 e.printStackTrace();
382 System.err.println( "Failed to access advanced config file." );
383 System.err.println( " Location: " + getAdvancedCfgLocation() );
384 System.exit( 1 );
385 }
386 }
387
388 // ------------------------------------------------------------------
389 // INSTANCE PARAMETERS AND ACCESSORS
390 // ------------------------------------------------------------------
391
392 // PARAMETERS
393 private String _childClassName;
394 private String _hostName;
395 private String _userName = "bob";
396 private String _password = "arglebargle";
397 private int _port;
398 private Socket _socket;
399 private CipherPartner _cipherSelf;
400 private InputStream _baseIn;
401 private OutputStream _baseOut;
402 private String _basicCfgLocation;
403 private String _advancedCfgLocation;
404 private String _serverHashesLocation;
405 private Properties _serverHashes;
406
407 // SETTERS
408 protected void setCipherSelf( CipherPartner self ) { _cipherSelf = self; }
409 protected void setChildClassName( String name ) { _childClassName = name; }
410 protected void setHostName( String name ) { _hostName = name; }
411 protected void setPort( int port ) { _port = port; }
412 protected void setSocket( Socket socket ) { _socket = socket; }
413 protected void setBasicCfgLocation( String location ) {
414 _basicCfgLocation = location;
415 }
416 protected void setAdvancedCfgLocation( String location ) {
417 _advancedCfgLocation = location;
418 }
419 protected void setServerHashesLocation( String location ) {
420 _serverHashesLocation = location;
421 }
422 protected void setServerHashes( Properties serverHashes ) {
423 _serverHashes = serverHashes;
424 }
425
426 protected void setBaseInputStream( InputStream baseIn ) {
427 _baseIn = baseIn;
428 }
429 protected void setBaseOutputStream( OutputStream baseOut ) {
430 _baseOut = baseOut;
431 }
432 public void setUserName( String userName ) {
433 _userName = userName;
434 }
435 public void setPassword( String password ) {
436 _password = password;
437 }
438
439 // GETTERS
440 /** Returns the childClassName that this client is configured for. */
441 public String getChildClassName() { return( _childClassName ); }
442 /** Returns the hostname that this client is configured for. */
443 public String getHostName() { return( _hostName ); }
444 /** Returns the port that this client is configured for. */
445 public int getPort() { return( _port ); }
446 /** Returns the InputStream that connects to your child's OutputStream */
447 public OutputStream getBaseOutputStream() { return( _baseOut ); }
448 /** Returns the OutputStream the connects to your child's InputStream */
449 public InputStream getBaseInputStream() { return( _baseIn ); }
450 public String getBasicCfgLocation() { return( _basicCfgLocation ); }
451 public String getAdvancedCfgLocation() { return( _advancedCfgLocation ); }
452 public String getServerHashesLocation() {
453 return( _serverHashesLocation );
454 }
455 public Properties getServerHashes() { return( _serverHashes ); }
456 public String getProviderName() { return( Server.DEFAULT_PROVIDER_NAME ); }
457 public String getUserName() { return( _userName ); }
458 public String getPassword() { return( _password ); }
459
460 protected Socket getSocket() { return( _socket ); }
461 protected CipherPartner getCipherSelf() { return( _cipherSelf ); }
462
463 // CONVENIENCE METHODS
464 protected Cipher getEncryptCipher() {
465 return( getCipherSelf().getEncryptCipher() );
466 }
467 protected Cipher getDecryptCipher() {
468 return( getCipherSelf().getDecryptCipher() );
469 }
470 protected byte[] getPublicEncoded() {
471 return( getCipherSelf().getPublicEncoded() );
472 }
473 protected byte[] getIv()
474 throws IOException {
475 return( getCipherSelf().getIv() );
476 }
477 public String getServerHash( String serverName ) {
478 return( (String)getServerHashes().get( serverName ) );
479 }
480 public void setServerHash( String serverName, String hashBase16 ) {
481 getServerHashes().put( serverName, hashBase16 );
482 }
483 }