/* TTPAP: "Trust The Prof Authentication Protocol" CSC 175 Programming Assignment Protocol Description and source code for the Trusted Authentication Server (TAS) Authentication, or anti-spoofing, is a key instrument of network and systems security. Authenticating someone by their ip address or DNS name is highly problematic. Packet filtering firewalls and NAT boxes are not enough, and can only be considered as a first line of defense. By lifting authentication up to its own layer of abstraction (above TCP/IP), we give ourselfs greater flexibility in implementing security measures. Authentication protocols depend on cryptography. There are two principal approaches: asymmetric, public key encryption such as found in SSL and HTTPS, and symmetric key encryption. In asymmetric encryption the key to encrypt and the key to decrypt are separate (one public, one private) and neither can be deduced from the other. However, we will use here a simplified variant of the Needham-Schroeder authentication protocol, which is described in chapter 8 of your textbook. Another, well-known variant of this protocol is Kerberos, which provides password authentication. This protocol uses symmetric encryption, where the encrpytion and decryption keys are either the same or can be easily inferred from one another. I have implemented such an encryption scheme based on the Feistel Cipher, which is the basis of many well-known algorithms including DES and AES. It use a 128-byte long key to both encrypt and decrpyt. Each "host" is given a unique key. A host is authenticated by verifying that it has the right key. The protocol requires at least three parties to work, one of which is a "trusted authentication server." This server (the TAS) is operated by a centralized system adminstrator and must be "trusted" by everyone. The TAS generates and distribute cipher keys to everyone. Only the TAS is in possession of everyone's key. Everyone else only have their own key. You will find in your home directory a .key file: 10.1.0.1: rottenapple.key 10.1.0.4: fancydessert.key 10.1.0.5: fruitbasket.key 10.1.0.6: nightmares.key 10.1.0.8: meltdowntown.key 10.1.0.9: crashedserver.key 10.1.0.12: upsidedown.key 10.1.0.3: thincrustslice.key (but thincrust will also run the TAS) 10.1.0.98: deepdishpie.key I deliberately chose names that are slightly different from your host names to emphasize that this form of authentication is independent of your DNS name or your ip address. You are authentic if you possess the right key. If you loose or corrupt your key, you can't be authenticated. If you want to pretend to be someone you're not, you'll have to steal someone else's key. So obviously, you must keep your key secret. Then how can you use it to encrypt/decrypt communications with another host? Each time before you initiate the communication, you must first request a "session key" from the TAS. The session key is generated by the TAS for each session, and is encrypted by the TAS using both your key and the key of the host you wish to communicate with. If both of you are authentic, they you will be able to decrypt the same session key, which you can then use to encrypt/decrypt messages between yourselfs. The protocol also has provisions for both you and your peer to verify eachother's identity. Specifically, let the three parties be: TAS: the trusted authentication server listening on TCP port 20002 (and running on 10.1.0.3) A: the active client that wishes to initiate a connection with ... B: a server that will listen on TCP port 30003 (it can listen on other ports as long as A knows what it is so A can connect to it). **** I've written the TAS. You will need to write A and B. **** Let the following abbreviations represent: Aname, Bname: the String IDs of the hosts, such as "rottenapple" AK, BK : the respective keys of hosts A and B, in 128-byte buffers SK: the "session key" generated by the TAS. AK(Aname): the string Aname encrypted using AK. This will be in a 32-byte byte[] buffer (char buffer[32]) AK(SK): the session key, itself encrypted using A's key. The encrypted form is also in a char buffer[128]. The protocol is defined by the following communications (if there are no errors) 1. A connects to TAS on port 20002 and sends: A's name in a 32-byte buffer (unencrypted) B's name in a 32-byte buffer (unencrypted) 2. TAS generates a session key SK, and sends back to A: AK(SK) : 128 bytes BK(SK) : 128 bytes BK(Aname): 32 bytes 3. A decryptes SK using it's own key AK. A connects to B on port 30003 and sends BK(SK) : 128 bytes (this is just the echo of the buffer from the TAS) BK(Aname): 32 bytes (again, this is just whatever was received above) SK(Aname): 32 bytes: this is formed by A after it decrypts SK 4. B decrypts SK and Aname using BK. With SK it decrypts SK(Aname), and checks that the Aname decrypted from the two 32-byte buffers are the same. If they are the same, then B can believe that A is authentic: A must have been able to decrypt the same SK in order to form SK(Aname). Also, since BK(Aname) was generated by the TAS, B can believe that A has the correct ID. A cannot fake BK(Aname) and SK(Aname) consistently without breaking BK. B now sends back to A: SK(Bname) : 32 bytes, plus a unencrpyted message in 128-byte buffer that says "you're authentic", or "hacker go away" or something of that nature. 5. A decrypts SK(Bname) and compares it with the expected Bname that it intended to connect with. If they're the same, then A can believe that B is authentic because it must have been able to decrypt SK, which means it must be in possession of BK. A now sends to B one final time, a unencrypted message in a 128-byte buffer that says "you're the real B" or "you're not the real B". 6... At this point, the authentication protocol is complete and if A and B are both authentic, they can now use the session key SK to encrypt and decrypt further communications. === Vulnerabilities: the strength of this protocol depends mainly on the strength of the encryption algorithm, and specifically on the "rounding" function F used in the Feistel cipher (see Feistel.java). The algorithm I implemented is not intended to be industrial strength, though it has the right theoretical properties. Note that from AK(SK) and BK(SK), party A will have a sample of what applying BK would look like: a weak cipher will make it easier for A to figure out what BK is, especially by requesting multiple session keys from the TAS in succession: with enough sample SK and BK(SK), it may eventually figure out what BK is, and therefore pretend to be B. To help prevent this from happening, in addition to using a very strong cipher algorithm, the TAS should attach a "time-to-live stamp" to the session key given to A (the ttl stamp will be part of encrypted buffers: AK(SK+ttlstamp) and BK(SK+ttlstamp)). A should use the same SK to communicate with B for the duration indicted by the ttl stamp (say a few days or a few hours). If A requests a SK from the TAS within this duration, it will be given the same SK. This dramatically slows down the rate that A can aquire sample SK's from the TAS for the purpose of breaking encryption: it also lessens the load on the TAS to service requests. However, I did not implement this refinement and therefore it may be possible for you to crack the encryption: it should be posssible because my cipher is weaker than DES, which has already been cracked. ======= Programming hints: All data must be stored inside byte[] buffers with a length that's divisible by 8, because of the requirements of the encryption algorithm. You will need to download three .java programs: Netutils.java (collection of public static utilities) Feistel.java (Feistel Cipher encryption/decryption) ttpapASMT.java (this file, which contains code for the TAS). You can download these and other useful files from the homepage, or by sftp guest@10.1.0.3 (or sftp -P 19003 guest@96.57.41.74), password 'csc175'. type ls to list files, get filename to download file, quit to quit. Place these .java files in the same directory and compile them. There are many public static functions in these programs that you can call. Some that you'll definitely need are: Netutils.bufToString and Netutils.StringToBuf: for encoding (but not encrypting) a string into a fixed length buffer, and to extract a string from such a buffer. Look at Netutils.java for how to call these functions. Netutils.readFully : fully reads a buffer from a DataInputStream ttpapASMT.readkey: reads a key from local file and store into a byte[] buffer (implementation and sample usage are in this file). ttpapASMT.makekey: creates a new random key, in case you wish to generate new keys for experimentation. To use the Feistel Cipher algorithm: load the encryption key into a 128-byte buffer (for example, using readkey), and create an instance of the Feistel class: Feistel Ft = new Feistel(key); // key is a 128-byte buffer You can use this object to encrpyt/decrypt byte[] buffers with buffer.length%8!=0. Given byte[] buf1 and byte[] buf2, to encrypt buf1 into buf2 (buf2 = key(buf1)): Ft.crypt(buf1, buf2, false); // or Ft.encrpyt(buf1,buf2), which is an alias And to decrypt buf1 into buf2 (buf2 will be in decrypted form): Ft.crypt(buf1,buf2,true); // or Ft.decrypt(buf1,buf2) The boolean argument is all that's needed to indicate if you want to encrypt or decrypt using the same key. Study the code for the TAS below to see how these functions are used together. The code contain other advanced elements (multi-threading) that I'll talk about later. =============== Protocol Communication Summary =============== In the following AK = key for A, BK = key for B, etc, and AK(X) means encrypt X using key for A... A -> TAS tcp port 20002: A's name in 32-byte buffer (unencrypted) B's name in 32-byte buffer (unencrypted) TAS -> A: generates 128 byte session key SK (124 random bytes + 4 offsets) Sends to A: AK(SK) : 128 BK(SK) : 128 BK(Aname): 32 bytes : (to make more secure, add timestamp) A --> B tcp port 30003: BK(SK) : 128 bytes : this is just the echo of what was received from TAS BK(Aname) : 32 bytes SK(Aname) : 32 bytes B --> A SK(Bname) : 32 bytes in 128-byte unencrypted buffer: "You're not really A, I don't trust you" or "You are the real A" A --> B in 128-byte unencrypted buffer: "Authentication complete" or "You're not the real B, you're not authentic" */ ///////TTPAP Trusted Authentication Server, Java Version, April 2020 import java.io.*; import java.net.*; // compile with Feistel.java, Netutils.java public class ttpapASMT { public static final int ROUNDS = 4; public static final int HALFSIZE = 4; // in bytes public static final int BLOCKSIZE = HALFSIZE*2; public static final int BASESIZE = 124; // number of random bytes public static final int KEYSIZE = BASESIZE+ROUNDS; public static final int NSIZE = 32; // name size // randomize buffer, with each having (byte)max value: public static void randomize(byte[] buf, int max) { for (int i=0;i0) { dst[di++] = src[si++]; l--; } return l; // 0 if copy completed } public static void genkey(byte[] key) { if (key.length!=KEYSIZE) return; randomize(key,256); for(int i = 0;i<4;i++) key[BASESIZE+i] = (byte)(Math.random()*BASESIZE); } public static void makekey(String id) { byte[] key = new byte[KEYSIZE]; genkey(key); try { DataOutputStream dout = new DataOutputStream(new FileOutputStream(id+".key")); dout.write(key,0,KEYSIZE); dout.close(); } catch (IOException e) { e.printStackTrace(); } }//makekey public static boolean readkey(String id, byte[] key) { boolean answer = false; try { DataInputStream din = new DataInputStream(new FileInputStream(id+".key")); if (key.length!=KEYSIZE) throw new Exception("bad buffer length "+key.length); Netutils.readFully(din,key); din.close(); answer= true; // true means success } catch (Exception e) { e.printStackTrace(); } return answer; // false on failure }//readkey ///////////////////// Simplified Encryption Routines: // encrypt a string into a byte[] buffer public static byte[] encryptString(Feistel encrypter, String s) { int n = s.length(); if (n%BLOCKSIZE!=0) n += BLOCKSIZE - (n%BLOCKSIZE); // pad byte[] buffer = new byte[n]; Netutils.StringToBuf(buffer,s,n); encrypter.encrypt(buffer); // destructive encrypt to buffer return buffer; }//encryptString public static String decryptString(Feistel decrypter, byte[] buffer) { if (buffer.length%BLOCKSIZE!=0) throw new RuntimeException("bad buffer"); decrypter.decrypt(buffer); return Netutils.bufToString(buffer,0,buffer.length); }//decryptString ///////////////////////////////////////////////////////////////// //////////////////// Trusted Authentication Server ////////////// static int TASPORT = 20002; public static void main(String[] args) throws Exception { if (args.length>0) TASPORT = Integer.parseInt(args[0]); ServerSocket sfd; Socket cfd; sfd = new ServerSocket(TASPORT); sfd.setReuseAddress(true); // comment out after debugging System.out.printf("TTPAP Authentication Server Started on Port %d...\n",TASPORT); while (true) { try { cfd = sfd.accept(); System.out.println("**TAS connection from "+cfd.getInetAddress()+":"+cfd.getPort()); clientthread CT = new clientthread(cfd); CT.start(); } catch(Exception e) {} }//server loop }//main }//ttpapASMT class clientthread extends Thread // a new thread is started for each client { static final int BASESIZE = 124; static final int ROUNDS = 4; static final int HALFSIZE = 4; static final int BLOCKSIZE=HALFSIZE*2; static final int KEYSIZE = BASESIZE+ROUNDS; static final int NSIZE = 32; Socket cfd; DataInputStream din, fin; DataOutputStream dout; byte[] name1 = new byte[NSIZE]; byte[] name2 = new byte[NSIZE]; // each key consists of 124 byte base + 4 byte offsets byte[] Akey = new byte[KEYSIZE]; byte[] Bkey = new byte[KEYSIZE]; byte[] Skey = new byte[KEYSIZE]; // session key byte[] outA = new byte[KEYSIZE]; byte[] outB = new byte[KEYSIZE]; byte[] Abuf = new byte[NSIZE]; public clientthread(Socket c) { cfd=c; } public void run() { try { cfd.setSoTimeout(5000); din = new DataInputStream(cfd.getInputStream()); dout = new DataOutputStream(cfd.getOutputStream()); // get names of A and B from A Netutils.readFully(din,name1); Netutils.readFully(din,name2); String A = Netutils.bufToString(name1,0,NSIZE); String B = Netutils.bufToString(name2,0,NSIZE); System.out.println("TAS: client A:"+A+" wants to connect to server B:"+B); // get keys for A and B from files: boolean aok = ttpapASMT.readkey(A,Akey); boolean bok = ttpapASMT.readkey(B,Bkey); if (!aok) throw new Exception("failed to read key for "+A); if (!bok) throw new Exception("failed to read key for "+B); ttpapASMT.genkey(Skey); // generate session key; Feistel EA = new Feistel(Akey); // 3 encrypters for 3 keys: Feistel EB = new Feistel(Bkey); Feistel ES = new Feistel(Skey); EA.crypt(Skey,outA,false); // false means encrypt, true means decrypt EB.crypt(Skey,outB,false); // same as EB.encrypt(Skey,outB); EB.crypt(name1,Abuf,false); // Abuf = BK(Aname) //Netutils.printbuf(Skey,0); // debug //Netutils.printbuf(outA,0); // send info back to A: dout.write(outA,0,KEYSIZE); dout.write(outB,0,KEYSIZE); dout.write(Abuf,0,NSIZE); System.out.println("Authentication info sent to client"); cfd.close(); } catch (Exception e) {System.out.println(e);} finally {try {cfd.close();} catch(Exception ee){}} }//run }//clientthread