001 package edu.harvard.deas.hyperenc; 002 003 import java.security.InvalidKeyException; 004 import java.security.MessageDigest; 005 import java.security.NoSuchAlgorithmException; 006 import java.util.ArrayList; 007 import java.util.Arrays; 008 import java.util.Collections; 009 import java.util.Date; 010 import java.util.HashMap; 011 import java.util.HashSet; 012 import java.util.LinkedList; 013 import java.util.List; 014 import java.util.Map; 015 import java.util.Set; 016 017 import javax.crypto.Mac; 018 019 import edu.harvard.deas.hyperenc.util.HexCoder; 020 import edu.harvard.deas.hyperenc.util.Pair; 021 import edu.harvard.deas.hyperenc.util.UUCoder; 022 023 /** 024 * Collects pages of random data, and stores pages, system blocks and encryption 025 * blocks for multiple contacts. Employs two {@link HyperStorage}s for each 026 * contact: one to hold pages and blocks for outgoing communication to that 027 * contact, and one to hold pages and blocks for incoming communication from 028 * that contact. 029 * <p> 030 * A HyperCollector also implements methods for creating new reconciliation 031 * messages and processing incoming reconciliation messages, and for encrypting 032 * and decrypting messages. 033 */ 034 public class HyperCollector { 035 /** Controls debugging output. */ 036 private static final boolean DEBUG = false; 037 038 /** Length of a block, in bytes. */ 039 public static final int BLOCK_LEN = 8; 040 041 /** Length of a page, in bytes. */ 042 public static final int PAGE_LEN = 4096; 043 044 /** 045 * Number of pages that should be combined to form a single page of a 046 * one-time pad. 047 */ 048 public static final int PAGES_PER_PAD = 30; 049 050 /** Number of system blocks to create each time a pad is created. */ 051 public static final int SYSBLOCKS_PER_PAD = 64; 052 053 // TODO these variables should all be included in some other object representing a system block 054 /** all the blocks necessary to make a new OTP */ 055 public final static int NUM_SYSBLOCKS = 4; 056 057 /** the block to be used as the random seed for our random source */ 058 public final static int RS_SEED_INDEX = 0; 059 060 /** the block to be used to encrypt the hash */ 061 public final static int HASH_ENC = 1; 062 063 /** the block to be used as the key to combine to pages into a OTP */ 064 public final static int PAGE_SHUF = 2; 065 066 /** number of blocks needed to compute a HEMAC */ 067 private final static int HEMAC_KEY_LENGTH = 8; 068 069 /** The e-mail contact of the owner of this HyperCollector. */ 070 private Contact myOwner; 071 072 /** 073 * Encryption and system block storage for outgoing messages. Keyed by the 074 * contact of the receiving party. 075 */ 076 private Map<Contact,HyperStorage> myMasterStorages; 077 078 /** 079 * Encryption and system block storage for incoming messages. Keyed by contact 080 * of the sending party. 081 */ 082 private Map<Contact,HyperStorage> mySlaveStorages; 083 084 /** 085 * Factory for creating new HyperStorages. 086 */ 087 private HyperStorageFactory myStorageFactory; 088 089 /** 090 * Constructs a new HyperCollector. A HyperCollector may be initialized with a 091 * set of HyperStorages; one way of keeping a persistent state across multiple 092 * sessions is to pass HyperStorages with a particular state to this 093 * constructor. 094 * <p> 095 * When a contact is added using the <code>newContact</code> method and 096 * HyperStorages for that contact have not been provided at construction, the 097 * <code>storageFactory</code> will be used to create new, empty HyperStorages 098 * for the new contact. 099 * 100 * @param masterstorages 101 * Initial set of outgoing block storages. 102 * @param slavestorages 103 * Initial set of incoming block storages. 104 * @param owner 105 * The owner of this Collector: the person who is the sender of 106 * outgoing messages and recipient of incoming messages. This is 107 * usually a Contact representing the user of the hyper-encryption 108 * client. 109 * @param storageFactory 110 * A factory for creating new HyperStorages. 111 */ 112 public HyperCollector(Map<Contact, HyperStorage> masterstorages, 113 Map<Contact, HyperStorage> slavestorages, 114 Contact owner, HyperStorageFactory storageFactory) { 115 myMasterStorages = new HashMap<Contact,HyperStorage>(masterstorages); 116 mySlaveStorages = new HashMap<Contact,HyperStorage>(slavestorages); 117 myOwner = owner; 118 myStorageFactory = storageFactory; 119 120 if (DEBUG) System.out.println("HyperCollector created."); 121 } 122 123 124 /** Get storage object for given partner and direction. */ 125 private HyperStorage getStorage(Contact contact, Direction direction) { 126 if (DEBUG) 127 System.out.println("Storage retreived."); 128 if (direction == Direction.MASTER) 129 return myMasterStorages.get(contact); 130 else 131 return mySlaveStorages.get(contact); 132 } 133 134 /** 135 * Gets the e-mail contact of the owner of this HyperCollector. The owner is 136 * the person who acts as the sender of messages encrypted by this 137 * HyperCollector, and as recipient of decrypted messages. 138 */ 139 public Contact getOwner() { 140 return myOwner; 141 } 142 143 /** 144 * Creates HyperStorages for the specified contact, if they don't exist 145 * already. (If they do exist, does nothing.) 146 * 147 * @param contact 148 * The new contact. 149 */ 150 private void newBlankContact(Contact contact) { 151 if (myMasterStorages.get(contact) != null) { 152 if (DEBUG) 153 System.err.println("Using existing HyperStorages..."); 154 return; 155 } 156 157 HyperStorage sto; 158 159 // make master storage for new contact 160 sto = myStorageFactory.getHyperStorage(contact, Direction.MASTER); 161 myMasterStorages.put(contact, sto); 162 163 // make slave storage for new contact 164 sto = myStorageFactory.getHyperStorage(contact, Direction.SLAVE); 165 mySlaveStorages.put(contact, sto); 166 } 167 168 /** 169 * Creates HyperStorages for contact, and initializes them with blocks created 170 * from the given shared secret. The first 128 bytes become 16 encryption 171 * blocks; all remaining bytes in the secret turn into 32-byte system blocks. 172 * If the number of bytes in either secret is not a multiple of the length of 173 * a system block, the extra bytes are discarded. 174 * <p> 175 * If the HyperStorages for the contact already exist, appends the given 176 * encryption and system blocks to the existing HyperStorages. 177 * 178 * @param contact 179 * The e-mail contact associated with the new contact. 180 * @param outgoingSecret 181 * A shared secret used to initialize outgoing communication with the 182 * contact. This secret is used to initialize encryption/system blocks 183 * for outgoing mail (the "master" blocks). 184 * @param incomingSecret 185 * A shared secret used to initialize incoming communication with the 186 * contact. This secret is used to initialize encryption/system blocks 187 * for incoming mail (the "slave" blocks). 188 */ 189 public void newContact(Contact contact, byte[] outgoingSecret, byte[] incomingSecret) { 190 191 // creates HyperStorages if they don't already exist 192 newBlankContact(contact); 193 194 try { 195 // Get newly-created outgoing block storage 196 HyperStorage outStorage = myMasterStorages.get(contact); 197 198 // Use shared secret to create initial blocks 199 200 // Make 16 encryption blocks 201 // XXX MAGIC NUMBERS ABOUND 202 for (int e = 0; e < 16; e++) { 203 byte outblock[] = new byte[BLOCK_LEN]; 204 outblock = Arrays.copyOfRange(outgoingSecret, BLOCK_LEN * e, BLOCK_LEN * (e+1)); 205 outStorage.addEncBlock(outblock); 206 } 207 208 // Make the remainder of the secret into system blocks 209 int noutblocks = outgoingSecret.length / NUM_SYSBLOCKS / BLOCK_LEN; 210 211 // Start at 4 because we used the first 4 blocks (128 bytes) of 212 // material to make encryption blocks above 213 for (int s = 4; s < noutblocks; s++) { 214 byte outblock[][] = new byte[NUM_SYSBLOCKS][BLOCK_LEN]; 215 final int SYSBLOCK_LEN = NUM_SYSBLOCKS * BLOCK_LEN; 216 for (int j = 0; j < NUM_SYSBLOCKS; j++) { 217 outblock[j] = Arrays.copyOfRange(outgoingSecret, 218 SYSBLOCK_LEN * s, SYSBLOCK_LEN * (s + 1)); 219 } 220 outStorage.addSysBlock(outblock); 221 } 222 223 224 // Repeat for the slave... 225 // Get newly-created incoming block storage 226 HyperStorage inStorage = mySlaveStorages.get(contact); 227 228 // Make 16 encryption blocks 229 // XXX MAGIC NUMBERS ABOUND 230 for (int e = 0; e < 16; e++) { 231 byte inblock[] = new byte[BLOCK_LEN]; 232 inblock = Arrays.copyOfRange(incomingSecret, BLOCK_LEN * e, BLOCK_LEN * (e+1)); 233 inStorage.addEncBlock(inblock); 234 } 235 236 // Make the remainder of the secret into system blocks 237 int ninblocks = incomingSecret.length / NUM_SYSBLOCKS / BLOCK_LEN; 238 239 // Start at 4 because we used the first 4 blocks (128 bytes) of 240 // material to make encryption blocks above 241 for (int s = 4; s < ninblocks; s++) { 242 byte inblock[][] = new byte[NUM_SYSBLOCKS][BLOCK_LEN]; 243 final int SYSBLOCK_LEN = NUM_SYSBLOCKS * BLOCK_LEN; 244 for (int j = 0; j < NUM_SYSBLOCKS; j++) { 245 inblock[j] = Arrays.copyOfRange(incomingSecret, 246 SYSBLOCK_LEN * s, SYSBLOCK_LEN * (s + 1)); 247 } 248 inStorage.addSysBlock(inblock); 249 } 250 251 fireHEvent(contact, Direction.MASTER, 252 HyperEncryptionEventType.BLOCK_STORAGE_UPDATED); 253 fireHEvent(contact, Direction.SLAVE, 254 HyperEncryptionEventType.BLOCK_STORAGE_UPDATED); 255 } catch(Exception e) { 256 throw new RuntimeException( 257 "Unexpected Exception when initializing HyperStorages for new contact " 258 + contact.toString(), e); 259 } 260 if (DEBUG) System.out.println("Contact added."); 261 } 262 263 264 /** 265 * Gets a page from a PSN, in the manner indicated by the system blocks. 266 * Computes the hash of the page, then stores both in the HyperStorage for 267 * <code>contact</code>. 268 * <p> 269 * This process consumes one system block from the HyperStorage for 270 * <code>contact</code>. 271 * <p> 272 * If unable to get a page from the PSN using a particular system block, this 273 * method tries each available system block until one succeeds. If all blocks 274 * fail to produce pages, then this method throws a PageException. 275 * 276 * @param contact 277 * Contact associated with this page. 278 * @param direction 279 * If <code>MASTER</code>, adds the collected page to the master page 280 * list, else to the slave page list. 281 * @throws EncryptionException 282 * If there are no system blocks available. 283 * @throws PageException 284 * If there are system blocks available, but all of them fail to 285 * produce a page 286 */ 287 public void collectNext(Contact contact, Direction direction) 288 throws EncryptionException, PageException { 289 HyperStorage myStorage = getStorage(contact, direction); 290 291 if (myStorage.numSysBlocks() == 0) 292 throw new EncryptionException("No system blocks available."); 293 294 List<Integer> mySysBlockList = myStorage.getSysBlockList(); 295 Collections.sort(mySysBlockList); 296 297 RandomSource rs = myStorage.getSource(); 298 MessageDigest md = myStorage.getDigest(); 299 PageShuffler ps = myStorage.getShuffler(); 300 301 int blockID = -1; 302 byte [][] usedBlock = null; 303 byte [] page = new byte[PAGE_LEN]; 304 305 for (int id : mySysBlockList) { 306 // Get the block with ID id 307 byte [][] block = myStorage.getSysBlock(id); 308 309 // Attempt to create page 310 try { 311 rs.getPage(block[RS_SEED_INDEX], page); 312 } catch (PageException e) { 313 // If failed, move on to the next system block, setting this one aside 314 // to be tried again later 315 continue; 316 } 317 318 // Upon success, save the block used and its ID, and stop iterating 319 usedBlock = block; 320 blockID = id; 321 break; 322 } 323 324 // If all the system blocks failed to produce a page, abort 325 if (usedBlock == null) { 326 throw new PageException("No system blocks resulted in pages"); 327 } 328 329 // Now that we've used the block, remove it from storage 330 myStorage.remSysBlock(blockID); 331 332 // Generate hash 333 byte [] hash = new byte[BLOCK_LEN]; 334 byte [] hashtemp = md.digest(page); 335 // Encrypt page hash by XORing the first BLOCK_LEN bytes of it against the 336 // designated part of the system block 337 for(int i = 0; i < BLOCK_LEN; i++) { 338 hash[i] = usedBlock[HASH_ENC][i]; 339 if(i < hashtemp.length) 340 hash[i] = (byte) (hash[i] ^ hashtemp[i]); 341 } 342 343 // Add hash and shuffled page to storage 344 myStorage.addUPage(blockID, ps.shufflePage(usedBlock[PAGE_SHUF], page), hash); 345 fireHEvent(contact, direction, HyperEncryptionEventType.BLOCK_STORAGE_UPDATED); 346 347 if (DEBUG) 348 System.out.println("Page Collected (ID " + blockID + ")"); 349 } 350 351 /** 352 * Returns a master reconciliation message containing an ID-hash pair for each 353 * unreconciled page in storage. This is the first step in the page 354 * reconciliation process. 355 * <p> 356 * The ID-hash pairs are in increasing order of their IDs. 357 * 358 * @param contact 359 * the contact with whom to reconcile pages. The reconciliation message 360 * will be sent to this contact, using the unreconciled pages 361 * associated with this contact. 362 * @param parser 363 * a MessageParser to be used to encode the master reconciliation hash 364 * list as a String 365 * @return a master reconciliation message (to be sent) 366 * @throws EncryptionException 367 * if there are not enough encryption blocks available to send this 368 * message (encryption blocks are required to authenticate the 369 * message) 370 */ 371 // TODO document why this is synchronized 372 synchronized public HyperMessage sendRec(Contact contact, MessageParser parser) 373 throws EncryptionException { 374 HyperStorage myStorage = getStorage(contact, Direction.MASTER); 375 int num = 0; 376 377 List<Pair<Integer,byte[]>> mreclist = new ArrayList<Pair<Integer,byte[]>>(); 378 379 List<Integer> sortedIDList = myStorage.getUPageList(); 380 Collections.sort(sortedIDList); // sort in place 381 382 for (int id : sortedIDList) { 383 // Get hash 384 byte[] b = myStorage.getUHash(id); 385 mreclist.add(new Pair<Integer,byte[]>(id, b)); 386 num++; 387 } 388 389 String messageBody = parser.mrecToString(mreclist); 390 391 int blocksAvailable = myStorage.numEncBlocks(); 392 if (blocksAvailable < HEMAC_KEY_LENGTH) 393 throw new EncryptionException( 394 "There are not enough encryption blocks available to authenticate this message (" 395 + HEMAC_KEY_LENGTH 396 + " required, but only have " 397 + blocksAvailable + ")."); 398 List<Integer> myEncBlockList = myStorage.getEncBlockList(); 399 List<Integer> macBlocksUsed = new ArrayList<Integer>(); 400 for (int k = 0; k < 8; k++) { 401 macBlocksUsed.add(myEncBlockList.get(k)); 402 } 403 // massage all whitespace 404 String nospaces = messageBody.trim().replaceAll("\\s+", "."); 405 HyperMAC hemac = computeHEMAC(nospaces.getBytes(), null, macBlocksUsed, myStorage); 406 for (int k = 0; k < 8; k++) 407 myStorage.remEncBlock(myEncBlockList.get(k)); 408 409 // Send message 410 HyperMessage mrec = HyperMessage.getInstance( 411 HyperMessageType.MASTER_REC, 412 myOwner, 413 contact, 414 "Master reconciliation", 415 messageBody, 416 null, 417 hemac, 418 new Date() // XXX use fake clock 419 ); 420 421 fireHEvent(contact, Direction.MASTER, 422 HyperEncryptionEventType.PAGE_SENT_FOR_RECONCILIATION); 423 if (DEBUG) 424 System.out.println("Master rec created."); 425 426 return mrec; 427 } 428 429 /** 430 * Make system and encryption blocks, if possible. If there are insufficiently 431 * many reconciled pages (i.e., less than PAGES_PER_PAD), an exception occurs. 432 * Otherwise, this method consumes PAGES_PER_PAD reconciled pages, creates 433 * SYSBLOCKS_PER_PAD system blocks, then creates as many encryption blocks as 434 * possible (specifically, PAGE_LEN/BLOCK_LEN - 435 * (SYSBLOCKS_PER_PAD*NUM_SYSBLOCKS) encryption blocks). 436 * 437 * @param contact 438 * the contact for whom to create blocks. The blocks will be produced 439 * from the reconciled pages associated with this contact. 440 * @param direction 441 * if <code>MASTER</code>, adds the produced blocks to the master block 442 * list, else to the slave block list. 443 * @throws EncryptionException 444 * if there are fewer than <code>PAGES_PER_PAD</code> reconciled pages 445 * available 446 * @throws IllegalArgumentException 447 * if <code>contact</code> is not a valid contact (specifically, 448 * if there are no pages or blocks stored) 449 */ 450 public void makeBlocks(Contact contact, Direction direction) 451 throws EncryptionException { 452 HyperStorage myStorage = getStorage(contact, direction); 453 454 if (myStorage == null) 455 throw new IllegalArgumentException("No HyperStorage found for contact " 456 + contact.toString()); 457 458 if (myStorage.numRPages() < PAGES_PER_PAD) 459 throw new EncryptionException("Insufficient number of reconciled pages (" 460 + PAGES_PER_PAD + " required, but " + myStorage.numRPages() 461 + " are available)"); 462 463 // XOR first PAGES_PER_PAD reconciled pages into OTP page 464 byte[] pad = myStorage.remRPage(); 465 for (int i = 1; i < PAGES_PER_PAD; i++) { 466 byte[] page = myStorage.remRPage(); 467 468 for (int j = 0; j < PAGE_LEN; j++) { 469 pad[j] = (byte) (pad[j] ^ page[j]); 470 } 471 } 472 473 // Break up OTP page into blocks 474 List<byte[]> blocks = new LinkedList<byte[]>(); 475 for (int i = 0; i < PAGE_LEN; i += BLOCK_LEN) { 476 byte[] block = new byte[BLOCK_LEN]; 477 for(int j = 0; j < BLOCK_LEN; j++) { 478 block[j] = pad[i+j]; 479 } 480 blocks.add(block); 481 } 482 483 // Given the blocks from the OTP, put SYSBLOCKS_PERPAD of them 484 // into sysBlocks to be system blocks, and all the rest into 485 // encBlocks to be used as sending blocks. 486 int numSys = 0, numEnc = 0; 487 byte[][] sysBlocks = new byte[NUM_SYSBLOCKS][BLOCK_LEN]; 488 for(int i = 0; i < SYSBLOCKS_PER_PAD; i++) { 489 for(int j = 0; j < NUM_SYSBLOCKS; j++) { 490 sysBlocks[j] = blocks.remove(0); 491 } 492 numSys++; 493 myStorage.addSysBlock(sysBlocks); 494 } 495 496 // Make encryption blocks 497 byte [] encBlock = new byte[BLOCK_LEN]; 498 while(blocks.size() != 0) { 499 encBlock = blocks.remove(0); 500 numEnc++; 501 myStorage.addEncBlock(encBlock); 502 } 503 504 fireHEvent(contact, direction, HyperEncryptionEventType.BLOCK_STORAGE_UPDATED); 505 506 if (DEBUG) 507 System.out.println("Blocks made."); 508 } 509 510 /** 511 * Given the contents of a master reconciliation message, returns a slave 512 * reconciliation message indicating the results of the reconciliation. This 513 * is the second step in the reconciliation process. 514 * 515 * @param contact 516 * the contact with whom to reconcile pages. The reconciliation 517 * response will be sent to this contact, and the reconciliation will 518 * be performed using the pages associated with this contact. 519 * @param message 520 * master reconciliation message 521 * @param parser 522 * a MessageParser to be used to parse <code>message</code> 523 * @return a slave reconciliation message describing the results of the 524 * reconciliation 525 * @throws MessageParseException 526 * if <code>parser</code> fails to parse <code>message</code> 527 */ 528 // FIXME thread safety problems 529 public HyperMessage reconcile(Contact contact, HyperMessage message, MessageParser parser) 530 throws MessageParseException { 531 HyperStorage myStorage = getStorage(contact, Direction.SLAVE); 532 Set<Integer> myUHashList = new HashSet<Integer>(myStorage.getUPageList()); 533 534 List<Pair<Integer,byte[]>> hashlist = parser.parseMRec(message.getContent()); 535 536 List<Pair<Integer,SlaveRecResult>> response = 537 new ArrayList<Pair<Integer,SlaveRecResult>>(); 538 539 int numPR = 0, numPFR = 0, numPRR = 0; 540 for (Pair<Integer,byte[]> pair : hashlist) { 541 int id = pair.first(); 542 byte[] hash = pair.second(); 543 544 if(myUHashList.contains(id)) { 545 if (DEBUG) System.out.println("Attempting to reconcile " + id + ": " + HexCoder.encode(myStorage.getUHash(id)) + " " + hash); 546 if(Arrays.equals(hash, myStorage.getUHash(id))) { 547 // Page positively reconciled 548 response.add(new Pair<Integer,SlaveRecResult>(id, SlaveRecResult.SUCCESS)); 549 numPR++; 550 551 // Add page to set of reconciled pages 552 myStorage.addRPage(myStorage.getUPage(id)); 553 } 554 else { 555 // Mark as negatively reconciled 556 response.add(new Pair<Integer,SlaveRecResult>(id, SlaveRecResult.FAILURE)); 557 numPFR++; 558 } 559 // Remove original unreconciled page/hash from storage 560 myStorage.remUPage(id); 561 } 562 else { 563 // Mark as unreconciled 564 response.add(new Pair<Integer,SlaveRecResult>(id, SlaveRecResult.NO_RESULT)); 565 numPRR++; 566 } 567 } 568 fireHEvent(contact, Direction.SLAVE, 569 HyperEncryptionEventType.BLOCK_STORAGE_UPDATED); 570 571 MessageParser mp = new PlainTextMessageParser(); 572 String messageBody = mp.srecToString(response); 573 574 575 // Use OUTGOING storage to send the response message 576 HyperStorage outStorage = getStorage(contact, Direction.MASTER); 577 578 List<Integer> myEncBlockList = outStorage.getEncBlockList(); 579 List<Integer> macBlocksUsed = new ArrayList<Integer>(); 580 for (int k = 0; k < 8; k++) 581 macBlocksUsed.add(myEncBlockList.get(k)); 582 // massage all whitespace 583 String nospaces = messageBody.trim().replaceAll("\\s+", "."); 584 HyperMAC hemac = computeHEMAC(nospaces.getBytes(), null, macBlocksUsed, outStorage); 585 for (int k = 0; k < 8; k++) 586 outStorage.remEncBlock(myEncBlockList.get(k)); 587 588 589 // Send response 590 HyperMessage srec = HyperMessage.getInstance( 591 HyperMessageType.SLAVE_REC, 592 myOwner, 593 contact, 594 "Slave reconciliation", 595 messageBody, 596 null, // not encrypted 597 hemac, 598 new Date() // XXX use fake clock 599 ); 600 601 if (DEBUG) System.out.println("Reconciled."); 602 603 return srec; 604 } 605 606 /** 607 * Process a slave reconciliation response. This is the third and final step 608 * in the reconciliation process. 609 * 610 * @param contact 611 * the contact with whom to reconcile pages. This is the contact from 612 * whom <code>message</code> was received; the reconciliation involves 613 * the pages associated with this contact. 614 * @param message 615 * response to reconciliation message 616 * @param parser 617 * a MessageParser to be used to parse <code>message</code> 618 * @throws MessageParseException 619 * if <code>parser</code> fails to parse <code>message</code> 620 */ 621 public void receiveRec(Contact contact, HyperMessage message, MessageParser parser) 622 throws MessageParseException { 623 HyperStorage myStorage = getStorage(contact, Direction.MASTER); 624 Set<Integer> myUPageList = new HashSet<Integer>(myStorage.getUPageList()); 625 626 List<Pair<Integer,SlaveRecResult>> resultlist = 627 parser.parseSRec(message.getContent()); 628 629 Integer id; 630 int numPR = 0, numPFR = 0, numPRR = 0; 631 for (Pair<Integer,SlaveRecResult> p : resultlist) { 632 id = p.first(); 633 634 switch (p.second()) { 635 case NO_RESULT: 636 numPRR++; 637 break; 638 case SUCCESS: 639 myStorage.addRPage(myStorage.getUPage(id)); 640 numPR++; 641 myStorage.remUPage(id); 642 break; 643 case FAILURE: 644 numPFR++; 645 myStorage.remUPage(id); 646 break; 647 default: 648 throw new IllegalArgumentException( 649 "Unknown reconciliation result code " + p.second()); 650 } 651 } 652 653 fireHEvent(contact, Direction.MASTER, 654 HyperEncryptionEventType.BLOCK_STORAGE_UPDATED); 655 656 if (DEBUG) 657 System.out.println("Response rec."); 658 } 659 660 /** 661 * Converts a list of integers to an array of their two's complement 662 * representations; each integer is expressed as a four-byte array 663 * (big-endian; the first element is the most significant byte), and the 664 * results are concatenated together into a single array. 665 * 666 * @param i 667 * integer to be converted 668 * @return a four-element byte array representation of <code>i</code> 669 */ 670 private byte[] intsToBytes(List<Integer> intList) { 671 byte[] result = new byte[4 * intList.size()]; 672 for (int index = 0; index < intList.size(); index++) { 673 int i = intList.get(index); 674 result[index*4 + 3] = (byte)( i & 0xFF ); 675 result[index*4 + 2] = (byte)( (i >> 8) & 0xFF ); 676 result[index*4 + 1] = (byte)( (i >> 16) & 0xFF ); 677 result[index*4 + 0] = (byte)( (i >> 24) & 0xFF ); 678 } 679 return result; 680 } 681 682 /** 683 * Computes the HEMAC of a message. If <code>blocksUsed</code> is present 684 * (non-null and non-empty), the contained integers are converted to 685 * big-endian four-byte arrays and appended to <code>msgBytes</code>. Then the 686 * encryption blocks whose IDs are specified by <code>macBlocksUsed</code> are 687 * concatenated together to form a 512-bit key, which is used to compute the 688 * MAC of <code>msgBytes</code>. 689 * 690 * @param msgBytes 691 * the message for which to compute the MAC 692 * @param blocksUsed 693 * if present, these integers are appended to <code>msgBytes</code> 694 * before the MAC is computed. This option exists to allow the 695 * encryption block ID list that accompanies the ciphertext to be 696 * authenticated as well. May be <code>null</code> or empty, in which 697 * case nothing is appended to <code>msgBytes</code>. 698 * @param macBlocksUsed 699 * a list of exactly 8 encryption block IDs. The corresponding 700 * encryption blocks will be concatenated, in order, to form the 701 * 512-bit HEMAC key. 702 * @param hstorage 703 * the HyperStorage from which the encryption blocks will be drawn. The 704 * encryption blocks <i>are not</i> removed from this HyperStorage; the 705 * caller must remove them afterwards, if desired. 706 * @return the computed MAC. The block list of the returned MAC, as given by 707 * {@link HyperMAC#getBlockList()}, is equal to (a copy of) 708 * <code>macBlocksUsed</code>. 709 * @throws IllegalArgumentException 710 * if any encryption block ID given in <code>macBlocksUsed</code> does 711 * not exist in <code>hstorage</code> 712 */ 713 private HyperMAC computeHEMAC(byte[] msgBytes, List<Integer> blocksUsed, 714 List<Integer> macBlocksUsed, HyperStorage hstorage) { 715 if (macBlocksUsed == null) { 716 throw new NullPointerException("macBlocksUsed may not be null"); 717 } 718 if (macBlocksUsed.size() != 8) { 719 throw new IllegalArgumentException( 720 "macBlocksUsed must have length 8, but has length " 721 + macBlocksUsed.size()); 722 } 723 724 // If necessary, append the block ID list to the end of the ciphertext 725 if (blocksUsed == null) 726 blocksUsed = new ArrayList<Integer>(); // replace with a length-zero list 727 int inputLen = msgBytes.length + 4 * blocksUsed.size(); 728 byte[] macInput = Arrays.copyOf(msgBytes, inputLen); 729 byte[] blockListBytes = intsToBytes(blocksUsed); 730 System.arraycopy(blockListBytes, 0, macInput, msgBytes.length, blockListBytes.length); 731 732 List<Integer> macBlockList = new ArrayList<Integer>(); 733 byte[] macKey = new byte[64]; 734 for (int b = 0; b < 8; b++) { 735 int nextBlockID = macBlocksUsed.remove(0); 736 byte[] nextBlock = hstorage.getEncBlock(nextBlockID); 737 if (nextBlock == null) 738 throw new IllegalArgumentException("Missing required block " + nextBlockID); 739 macBlockList.add(nextBlockID); 740 System.arraycopy(nextBlock, 0, macKey, 8 * b, 8); 741 } 742 743 Mac hmac; 744 try { 745 hmac = Mac.getInstance("HmacSHA256"); 746 } catch (NoSuchAlgorithmException e) { 747 throw new RuntimeException("Could not create HMAC-SHA256 instance.", e); 748 } 749 750 byte[] macBytes; 751 try { 752 macBytes = HyperMAC.computeMac(hmac, macKey, macInput); 753 } catch (InvalidKeyException e) { 754 throw new RuntimeException("Could not compute HEMAC.", e); 755 } 756 757 return new HyperMAC(macBlockList, macBytes); 758 } 759 760 /** 761 * Verifies the HEMAC of a message. Computes what the HEMAC should be, given 762 * the parameters, and compares the result to <code>expected</code>. 763 * <p> 764 * If <code>blocksUsed</code> is present (non-null and non-empty), the 765 * contained integers are converted to big-endian four-byte arrays and 766 * appended to <code>msgBytes</code>. Then the encryption blocks whose IDs are 767 * specified by <code>expected.getBlockList()</code> are concatenated together 768 * to form a 512-bit key, which is used to compute the MAC of 769 * <code>msgBytes</code>. The encryption blocks are drawn from the <i>incoming 770 * block storage</i> for <code>contact</code>. 771 * 772 * @param msgBytes 773 * the message for which to compute the MAC 774 * @param blocksUsed 775 * if present, these integers are appended to <code>msgBytes</code> 776 * before the MAC is computed. This option exists to allow the 777 * encryption block ID list that accompanies the ciphertext to be 778 * authenticated as well. May be <code>null</code> or empty, in which 779 * case nothing is appended to <code>msgBytes</code>. 780 * @param expected 781 * the MAC to be verified. If the computed MAC equals 782 * <code>expected.getMac()</code>, then <code>expected</code> is said 783 * to be <i>valid</i>. If not, it is <i>invalid</i>. 784 * @param contact 785 * the Contact from whom the message and MAC were received. The 786 * encryption blocks from the incoming HyperStorage for this contact 787 * are used in computing the MAC. The encryption blocks are removed 788 * from this HyperStorage <i>if and only if</i> the MAC verifies. If 789 * the <code>expected</code> MAC does not match the computed MAC, the 790 * HyperStorage is left unchanged. afterwards, if desired. 791 * @return <code>true</code> iff the <code>expected</code> MAC is valid. 792 * @throws BlockMissingException 793 * if one or more of the encryption block IDs given in 794 * <code>expected.getBlockList()</code> is not present in the 795 * HyperStorage for <code>contact</code>. If this exception is thrown, 796 * the MAC is considered to be invalid. 797 */ 798 public boolean verifyHEMAC(byte[] msgBytes, List<Integer> blocksUsed, 799 HyperMAC expected, Contact contact) 800 throws BlockMissingException { 801 HyperStorage hstorage = getStorage(contact, Direction.SLAVE); 802 if (expected == null) 803 return false; 804 805 // If any block is missing, should fail 806 for (int id : expected.getBlockList()) { 807 byte[] blk = hstorage.getEncBlock(id); 808 if (blk == null) 809 throw new BlockMissingException(Arrays.asList(id), 810 "Block " + id + " is missing"); 811 } 812 813 HyperMAC actual = computeHEMAC(msgBytes, blocksUsed, expected.getBlockList(), hstorage); 814 815 // If the MAC fails, abort. Otherwise, remove the used blocks from storage. 816 if (!Arrays.equals(actual.getMac(), expected.getMac())) { 817 return false; 818 } else { 819 try { 820 hstorage.remEncBlockList(expected.getBlockList()); 821 } catch (BlockMissingException e) { 822 // FIXME this is possible and undesirable! find a way to fix it. 823 throw new RuntimeException( 824 "A block disappeared since the time that we used it!", e); 825 } 826 return true; 827 } 828 } 829 830 831 832 /** 833 * Encrypts a message. The content of <code>unencrypted</code> is encrypted 834 * using the encryption blocks associated with the recipient of 835 * <code>unencrypted</code>. The subject is <i>not</i> encrypted. 836 * <p> 837 * The returned HyperMessage has the same subject, sender, recipient, and date 838 * as the given unencrypted HyperMessage. Its type is 839 * {@link HyperMessageType#ENCRYPTED}. Its content is the encrypted content of 840 * <code>unencrypted</code>, and its <i>padsUsed</i> field is the list of 841 * encryption block IDs used to encrypt the content. 842 * 843 * @param unencrypted 844 * a HyperMessage containing the message to be encrypted 845 * @throws EncryptionException 846 * if there are not enough encryption blocks available to encrypt this 847 * message 848 * @throws IllegalArgumentException 849 * if <code>unencrypted</code> is already encrypted 850 * @return A HyperMessage containing the encrypted message. 851 */ 852 public HyperMessage encrypt(HyperMessage unencrypted) 853 throws EncryptionException { 854 if (unencrypted.getPadsUsed() != null) 855 throw new IllegalArgumentException( 856 "The given HyperMessage is already encrypted!"); 857 858 HyperStorage myStorage = getStorage(unencrypted.getRecipient(), 859 Direction.MASTER); 860 861 byte [] msgBytes = unencrypted.getContent().getBytes(); 862 int numBlocks = (msgBytes.length - 1) / BLOCK_LEN + 1 + HEMAC_KEY_LENGTH; 863 while (numBlocks > myStorage.numEncBlocks()) { 864 throw new EncryptionException( 865 "Insufficient encryption blocks available to encrypt message! (" 866 + numBlocks + " are requried, but only " 867 + myStorage.numEncBlocks() + " are available.)"); 868 } 869 870 List<Integer> blocksUsed = new ArrayList<Integer>(); 871 872 // FIXME in the presence of multiple threads, this will probably result in 873 // an attempt to use a block multiple times, which will cause a NullPointerException! 874 // TODO is there a good reason to keep this local copy? 875 List<Integer> myEncBlockList = myStorage.getEncBlockList(); 876 877 // Encrypt msg in-place, byte by byte 878 byte [] block = null; 879 for(int i = 0; i < msgBytes.length; i++) { 880 int ID; 881 int index = i % BLOCK_LEN; 882 883 if(index == 0) { 884 // Extract next encryption block 885 ID = myEncBlockList.remove(0); 886 block = myStorage.remEncBlock(ID); 887 blocksUsed.add(ID); 888 } 889 890 msgBytes[i] = (byte) (msgBytes[i] ^ block[index]); 891 } 892 893 String encodedMessage = UUCoder.encode(msgBytes); 894 895 // Compute MAC -- once done, remove the blocks we used 896 List<Integer> macBlocksUsed = new ArrayList<Integer>(); 897 for (int k = 0; k < 8; k++) 898 macBlocksUsed.add(myEncBlockList.get(k)); 899 HyperMAC hemac = computeHEMAC(msgBytes, blocksUsed, macBlocksUsed, myStorage); 900 for (int k = 0; k < 8; k++) 901 myStorage.remEncBlock(myEncBlockList.get(k)); 902 903 HyperMessage hm = HyperMessage.getInstance( 904 HyperMessageType.ENCRYPTED, 905 unencrypted.getSender(), 906 unencrypted.getRecipient(), 907 unencrypted.getSubject(), 908 encodedMessage, 909 blocksUsed, 910 hemac, 911 unencrypted.getDate() 912 ); 913 914 fireHEvent(unencrypted.getRecipient(), Direction.MASTER, 915 HyperEncryptionEventType.BLOCK_STORAGE_UPDATED); 916 917 return hm; 918 } 919 920 /** 921 * Decrypt message. Uses the encryption blocks associated with the sender of 922 * the encrypted message (as determined by the <code>getSender</code> method). 923 * <p> 924 * The returned HyperMessage has the same subject, sender, recipient, and date 925 * as the given encrypted HyperMessage. Its type is 926 * {@link HyperMessageType#UNENCRYPTED}. Its content is the decrypted content 927 * of <code>encryptedMessage</code>, and its <i>padsUsed</i> field is 928 * <code>null</code>. 929 * 930 * @param encryptedMessage 931 * message to decrypt 932 * @throws BlockMissingException 933 * if an encryption block required for decryption is not available 934 * @return A HyperMessage containing the decrypted message. 935 */ 936 public HyperMessage decrypt(HyperMessage encryptedMessage) 937 throws BlockMissingException { 938 Contact sender = encryptedMessage.getSender(); 939 HyperStorage myStorage = getStorage(sender, Direction.SLAVE); 940 941 byte [] msgBytes = UUCoder.decode(encryptedMessage.getContent()); 942 int numBlocks = (msgBytes.length - 1) / BLOCK_LEN + 1; 943 944 List<Integer> blocksUsed = encryptedMessage.getPadsUsed(); 945 946 // throws a BlockMissingException if a needed encryption block is missing 947 List<byte[]> encBlockList = myStorage.remEncBlockList(blocksUsed); 948 949 for (int blockNum = 0; blockNum < numBlocks; blockNum++) { 950 byte[] block = encBlockList.get(blockNum); 951 assert(block.length == BLOCK_LEN); 952 for (int byteNum = 0; byteNum < BLOCK_LEN; byteNum++) { 953 int bi = blockNum*BLOCK_LEN + byteNum; 954 if (bi < msgBytes.length) 955 msgBytes[bi] = (byte) (msgBytes[bi] ^ block[byteNum]); 956 } 957 } 958 959 fireHEvent(sender, Direction.SLAVE, 960 HyperEncryptionEventType.BLOCK_STORAGE_UPDATED); 961 962 HyperMessage hm = HyperMessage.getInstance( 963 HyperMessageType.UNENCRYPTED, 964 encryptedMessage.getSender(), 965 encryptedMessage.getRecipient(), 966 encryptedMessage.getSubject(), 967 new String(msgBytes), 968 null, 969 null, 970 encryptedMessage.getDate() 971 ); 972 973 return hm; 974 } 975 976 /** 977 * Returns <code>true</code> iff this collector has storages for the given contact. 978 * 979 * @param c a Contact 980 * @return if this collector has storages for <code>c</code> 981 */ 982 public boolean hasContact(Contact c) { 983 HyperStorage m = getStorage(c, Direction.MASTER); 984 HyperStorage s = getStorage(c, Direction.SLAVE); 985 return (m != null) && (s != null); 986 } 987 988 /** 989 * Returns the number of system blocks in storage for the given contact and 990 * direction. 991 * 992 * @param contact 993 * contact 994 * @param dir 995 * direction of communication 996 * @return the number of system blocks available for communication with the 997 * given contact in the given direction 998 * @throws IllegalArgumentException 999 * if storages for <code>contact</code> do not exist. Check whether 1000 * this is the case using <code>hasContact</code>. To fix this, create 1001 * the contact using <code>newContact</code>. 1002 */ 1003 public int numSysBlocks(Contact contact, Direction dir) { 1004 HyperStorage s = getStorage(contact, dir); 1005 if (s == null) 1006 throw new IllegalArgumentException("illegal contact " + contact.toString()); 1007 return s.numSysBlocks(); 1008 } 1009 1010 1011 /** 1012 * Returns the number of encryption blocks in storage for the given contact and 1013 * direction. 1014 * 1015 * @param contact 1016 * contact 1017 * @param dir 1018 * direction of communication 1019 * @return the number of encryption blocks available for communication with the 1020 * given contact in the given direction 1021 * @throws IllegalArgumentException 1022 * if storages for <code>contact</code> do not exist. Check whether 1023 * this is the case using <code>hasContact</code>. To fix this, create 1024 * the contact using <code>newContact</code>. 1025 */ 1026 public int numEncBlocks(Contact contact, Direction dir) { 1027 HyperStorage s = getStorage(contact, dir); 1028 if (s == null) 1029 throw new IllegalArgumentException("illegal contact " + contact.toString()); 1030 return s.numEncBlocks(); 1031 } 1032 1033 1034 /** 1035 * Returns the number of unreconciled pages in storage for the given contact and 1036 * direction. 1037 * 1038 * @param contact 1039 * contact 1040 * @param dir 1041 * direction of communication 1042 * @return the number of unreconciled pages available for communication with the 1043 * given contact in the given direction 1044 * @throws IllegalArgumentException 1045 * if storages for <code>contact</code> do not exist. Check whether 1046 * this is the case using <code>hasContact</code>. To fix this, create 1047 * the contact using <code>newContact</code>. 1048 */ 1049 public int numUPages(Contact contact, Direction dir) { 1050 HyperStorage s = getStorage(contact, dir); 1051 if (s == null) 1052 throw new IllegalArgumentException("illegal contact " + contact.toString()); 1053 return s.numUPages(); 1054 } 1055 1056 1057 /** 1058 * Returns the number of reconciled pages in storage for the given contact and 1059 * direction. 1060 * 1061 * @param contact 1062 * contact 1063 * @param dir 1064 * direction of communication 1065 * @return the number of reconciled pages available for communication with the 1066 * given contact in the given direction 1067 * @throws IllegalArgumentException 1068 * if storages for <code>contact</code> do not exist. Check whether 1069 * this is the case using <code>hasContact</code>. To fix this, create 1070 * the contact using <code>newContact</code>. 1071 */ 1072 public int numRPages(Contact contact, Direction dir) { 1073 HyperStorage s = getStorage(contact, dir); 1074 if (s == null) 1075 throw new IllegalArgumentException("illegal contact " + contact.toString()); 1076 return s.numRPages(); 1077 } 1078 1079 1080 javax.swing.event.EventListenerList hListeners = new 1081 javax.swing.event.EventListenerList(); 1082 1083 /** 1084 * Register interest in HyperEncryptionEvents. <code>listener</code> will 1085 * receive events fired from this HyperCollector. 1086 */ 1087 public void addHListener(HyperEncryptionListener listener) 1088 { 1089 hListeners.add(HyperEncryptionListener.class, listener); 1090 } 1091 1092 /** 1093 * Unregister the given as a listener for this object. <code>listener</code> 1094 * will no longer receive events fired from this HyperCollector. If 1095 * <code>listener</code> was not listening to this HyperCollector, does 1096 * nothing. 1097 */ 1098 public void removeHListener(HyperEncryptionListener listener) 1099 { 1100 hListeners.remove(HyperEncryptionListener.class, listener); 1101 } 1102 1103 /** 1104 * Alert all listeners that a HyperEncryptionEvent has occurred. 1105 * 1106 * @param contact 1107 * the contact whose data was affected by the event being fired 1108 * @param direction 1109 * whether the event was generated by a master or a slave storage 1110 * @param type 1111 * Indicates the type of event that occurred. 1112 */ 1113 protected void fireHEvent(Contact contact, Direction direction, 1114 HyperEncryptionEventType type) 1115 { 1116 HyperEncryptionEvent event = 1117 new HyperEncryptionEvent(this, contact, direction, type); 1118 HyperEncryptionListener[] listeners = 1119 hListeners.getListeners(HyperEncryptionListener.class);; 1120 1121 for (HyperEncryptionListener l : listeners) { 1122 l.eventFired(event); 1123 } 1124 } 1125 }