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    }