Package ch.ethz.inf.vs.scandium.dtls

Source Code of ch.ethz.inf.vs.scandium.dtls.Handshaker

/*******************************************************************************
* Copyright (c) 2014, Institute for Pervasive Computing, ETH Zurich.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
*    notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
*    notice, this list of conditions and the following disclaimer in the
*    documentation and/or other materials provided with the distribution.
* 3. Neither the name of the Institute nor the names of its contributors
*    may be used to endorse or promote products derived from this software
*    without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* This file is part of the Scandium (Sc) Security for Californium.
******************************************************************************/
package ch.ethz.inf.vs.scandium.dtls;

import java.io.FileInputStream;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.InetSocketAddress;
import java.security.KeyStore;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.cert.Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;

import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import ch.ethz.inf.vs.elements.RawData;
import ch.ethz.inf.vs.scandium.DTLSConnector;
import ch.ethz.inf.vs.scandium.dtls.CipherSuite.KeyExchangeAlgorithm;
import ch.ethz.inf.vs.scandium.util.ScProperties;

/**
* The base class for the handshake protocol logic. Contains all the
* functionality and fields which is needed by all types of handshakers.
*
* @author Stefan Jucker
*
*/
public abstract class Handshaker {

  // Logging ////////////////////////////////////////////////////////

  protected static final Logger LOGGER = Logger.getLogger(Handshaker.class.getCanonicalName());

  // Static members /////////////////////////////////////////////////

  private final static int MASTER_SECRET_LABEL = 1;

  private final static int KEY_EXPANSION_LABEL = 2;

  public final static int CLIENT_FINISHED_LABEL = 3;

  public final static int SERVER_FINISHED_LABEL = 4;

  public final static int TEST_LABEL = 5;

  public final static int TEST_LABEL_2 = 6;

  public final static int TEST_LABEL_3 = 7;
 
  public final static String KEY_STORE_PASSWORD = "endPass";
 
  private static final String TRUST_STORE_PASSWORD = "rootPass";

  /**
   * A map storing shared keys. The shared key is associated with an PSK
   * identity. See <a href="http://tools.ietf.org/html/rfc4279#section-2">RFC
   * 4279</a> for details.
   */
  protected static Map<String, byte[]> sharedKeys = new HashMap<String, byte[]>();

  static {
    try {
      sharedKeys.put("password", "sesame".getBytes("US-ASCII"));
      sharedKeys.put("Client", "Client".getBytes("US-ASCII"));
      sharedKeys.put("Server", "Server".getBytes("US-ASCII"));
      sharedKeys.put("PSK_Identity", new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 });
    } catch (UnsupportedEncodingException e) {
      LOGGER.severe("Unsupported Encoding in given PSKs.");
    }
  }

  // Members ////////////////////////////////////////////////////////

  /**
   * Indicates, whether the handshake protocol is performed from the client's
   * side or the server's.
   */
  protected boolean isClient;

  protected int state = -1;

  protected InetSocketAddress endpointAddress;

  protected ProtocolVersion usedProtocol;
  protected Random clientRandom;
  protected Random serverRandom;
  private CipherSuite cipherSuite;
  private CompressionMethod compressionMethod;

  protected KeyExchangeAlgorithm keyExchange;

  /** The helper class to execute the ECDHE key agreement and key generation. */
  protected ECDHECryptography ecdhe;

  private byte[] masterSecret;

  private SecretKey clientWriteMACKey;
  private SecretKey serverWriteMACKey;

  private IvParameterSpec clientWriteIV;
  private IvParameterSpec serverWriteIV;

  private SecretKey clientWriteKey;
  private SecretKey serverWriteKey;

  protected DTLSSession session = null;

  /**
   * The current sequence number (in the handshake message called message_seq)
   * for this handshake.
   */
  private int sequenceNumber = 0;

  /** The next expected handshake message sequence number. */
  private int nextReceiveSeq = 0;

  /** The application data that needs encryption. */
  protected RawData message = null;

  /** Queue for messages, that can not yet be processed. */
  protected Collection<Record> queuedMessages;
 
  /** Store the fragmented messages until we are able to reassemble the handshake message. */
  protected Map<Integer, List<FragmentedHandshakeMessage>> fragmentedMessages = new HashMap<Integer, List<FragmentedHandshakeMessage>>();

  /**
   * The message digest to compute the handshake hashes sent in the
   * {@link Finished} messages.
   */
  protected MessageDigest md;

  /** All the handshake messages sent before the CertificateVerify message. */
  protected byte[] handshakeMessages = new byte[] {};

  /**
   * The last flight that is sent during this handshake, will not be
   * retransmitted unless the peer retransmits its last flight.
   */
  protected DTLSFlight lastFlight = null;

  /** The handshaker's private key. */
  protected PrivateKey privateKey;

  /** The handshaker's certificate chain. */
  protected Certificate[] certificates;

  // Constructor ////////////////////////////////////////////////////

  /**
   *
   * @param peerAddress
   *            the peer's address.
   * @param isClient
   *            indicating whether this instance represents a client or a
   *            server.
   * @param session
   *            the session belonging to this handshake.
   */
  public Handshaker(InetSocketAddress peerAddress, boolean isClient, DTLSSession session) {
    this.endpointAddress = peerAddress;
    this.isClient = isClient;
    this.session = session;
    this.queuedMessages = new HashSet<Record>();
    loadKeyStore();
    try {
      this.md = MessageDigest.getInstance("SHA-256");
    } catch (NoSuchAlgorithmException e) {
      LOGGER.severe("Could not initialize the message digest algorithm.");
      e.printStackTrace();
    }
  }

  // Abstract Methods ///////////////////////////////////////////////

  /**
   * Processes the handshake message according to its {@link HandshakeType}
   * and reacts according to the protocol specification.
   *
   * @param message
   *            the received {@link HandshakeMessage}.
   * @return the list all handshake messages that need to be sent triggered by
   *         this message.
   */
  public abstract DTLSFlight processMessage(Record message) throws HandshakeException;

  /**
   * Gets the handshake flight which needs to be sent first to initiate
   * handshake. This differs from client side to server side.
   *
   * @return the handshake message to start off the handshake protocol.
   */
  public abstract DTLSFlight getStartHandshakeMessage();

  // Methods ////////////////////////////////////////////////////////

  /**
   * First, generates the master secret from the given premaster secret and
   * then applying the key expansion on the master secret generates a large
   * enough key block to generate the write, MAC and IV keys. See <a
   * href="http://tools.ietf.org/html/rfc5246#section-6.3">RFC 5246</a> for
   * further details about the keys.
   *
   * @param premasterSecret
   *            the shared premaster secret.
   */
  protected void generateKeys(byte[] premasterSecret) {
    masterSecret = generateMasterSecret(premasterSecret);
    session.setMasterSecret(masterSecret);

    calculateKeys(masterSecret);
  }

  /**
   * Calculates the encryption key, MAC key and IV from a given master secret.
   * First, applies the key expansion to the master secret.
   *
   * @param masterSecret
   *            the master secret.
   */
  private void calculateKeys(byte[] masterSecret) {
    /*
     * See http://tools.ietf.org/html/rfc5246#section-6.3:
     * key_block = PRF(SecurityParameters.master_secret, "key expansion", SecurityParameters.server_random + SecurityParameters.client_random);
     */

    byte[] data = doPRF(masterSecret, KEY_EXPANSION_LABEL, ByteArrayUtils.concatenate(serverRandom.getRandomBytes(), clientRandom.getRandomBytes()));

    /*
     * Create keys as suggested in
     * http://tools.ietf.org/html/rfc5246#section-6.3:
     * client_write_MAC_key[SecurityParameters.mac_key_length]
     * server_write_MAC_key[SecurityParameters.mac_key_length]
     * client_write_key[SecurityParameters.enc_key_length]
     * server_write_key[SecurityParameters.enc_key_length]
     * client_write_IV[SecurityParameters.fixed_iv_length]
     * server_write_IV[SecurityParameters.fixed_iv_length]
     */
    if (cipherSuite == null) {
      cipherSuite = session.getCipherSuite();
    }

    int macKeyLength = cipherSuite.getBulkCipher().getMacKeyLength();
    int encKeyLength = cipherSuite.getBulkCipher().getEncKeyLength();
    int fixedIvLength = cipherSuite.getBulkCipher().getFixedIvLength();

    clientWriteMACKey = new SecretKeySpec(data, 0, macKeyLength, "Mac");
    serverWriteMACKey = new SecretKeySpec(data, macKeyLength, macKeyLength, "Mac");

    clientWriteKey = new SecretKeySpec(data, 2 * macKeyLength, encKeyLength, "AES");
    serverWriteKey = new SecretKeySpec(data, (2 * macKeyLength) + encKeyLength, encKeyLength, "AES");

    clientWriteIV = new IvParameterSpec(data, (2 * macKeyLength) + (2 * encKeyLength), fixedIvLength);
    serverWriteIV = new IvParameterSpec(data, (2 * macKeyLength) + (2 * encKeyLength) + fixedIvLength, fixedIvLength);

  }

  /**
   * Generates the master secret from a given shared premaster secret as
   * described in <a href="http://tools.ietf.org/html/rfc5246#section-8.1">RFC
   * 5246</a>.
   *
   * <pre>
   * master_secret = PRF(pre_master_secret, "master secret",
   *   ClientHello.random + ServerHello.random) [0..47]
   * </pre>
   *
   * @param premasterSecret
   *            the shared premaster secret.
   * @return the master secret.
   */
  private byte[] generateMasterSecret(byte[] premasterSecret) {
    byte[] randomSeed = ByteArrayUtils.concatenate(clientRandom.getRandomBytes(), serverRandom.getRandomBytes());
    return doPRF(premasterSecret, MASTER_SECRET_LABEL, randomSeed);
  }

  /**
   * See <a href="http://tools.ietf.org/html/rfc4279#section-2">RFC 4279</a>:
   * The premaster secret is formed as follows: if the PSK is N octets long,
   * concatenate a uint16 with the value N, N zero octets, a second uint16
   * with the value N, and the PSK itself.
   *
   * @param psk
   *            the preshared key as byte array.
   * @return the premaster secret.
   */
  protected byte[] generatePremasterSecretFromPSK(byte[] psk) {
    /*
     * What we are building is the following with length fields in between:
     * struct { opaque other_secret<0..2^16-1>; opaque psk<0..2^16-1>; };
     */
    int length = psk.length;

    byte[] lengthField = new byte[2];
    lengthField[0] = (byte) (length >> 8);
    lengthField[1] = (byte) (length);

    byte[] zero = ByteArrayUtils.padArray(new byte[0], (byte) 0x00, length);

    byte[] premasterSecret = ByteArrayUtils.concatenate(lengthField, ByteArrayUtils.concatenate(zero, ByteArrayUtils.concatenate(lengthField, psk)));

    return premasterSecret;
  }

  /**
   * Does the Pseudorandom function as defined in <a
   * href="http://tools.ietf.org/html/rfc5246#section-5">RFC 5246</a>.
   *
   * @param secret
   *            the secret
   * @param labelId
   *            the label
   * @param seed
   *            the seed
   * @return the byte[]
   */
  public static byte[] doPRF(byte[] secret, int labelId, byte[] seed) {
    try {
      MessageDigest md = MessageDigest.getInstance("SHA-256");

      String label;
      switch (labelId) {
      case MASTER_SECRET_LABEL:
        // The master secret is always 48 bytes long, see
        // http://tools.ietf.org/html/rfc5246#section-8.1
        label = "master secret";
        return doExpansion(md, secret, ByteArrayUtils.concatenate(label.getBytes(), seed), 48);

      case KEY_EXPANSION_LABEL:
        // The most key material required is 128 bytes, see
        // http://tools.ietf.org/html/rfc5246#section-6.3
        label = "key expansion";
        return doExpansion(md, secret, ByteArrayUtils.concatenate(label.getBytes(), seed), 128);

      case CLIENT_FINISHED_LABEL:
        // The verify data is always 12 bytes long, see
        // http://tools.ietf.org/html/rfc5246#section-7.4.9
        label = "client finished";
        return doExpansion(md, secret, ByteArrayUtils.concatenate(label.getBytes(), seed), 12);

      case SERVER_FINISHED_LABEL:
        // The verify data is always 12 bytes long, see
        // http://tools.ietf.org/html/rfc5246#section-7.4.9
        label = "server finished";
        return doExpansion(md, secret, ByteArrayUtils.concatenate(label.getBytes(), seed), 12);

      case TEST_LABEL:
        // http://www.ietf.org/mail-archive/web/tls/current/msg03416.html
        label = "test label";
        return doExpansion(md, secret, ByteArrayUtils.concatenate(label.getBytes(), seed), 100);

      case TEST_LABEL_2:
        // http://www.ietf.org/mail-archive/web/tls/current/msg03416.html
        label = "test label";
        md = MessageDigest.getInstance("SHA-512");
        return doExpansion(md, secret, ByteArrayUtils.concatenate(label.getBytes(), seed), 196);

      case TEST_LABEL_3:
        // http://www.ietf.org/mail-archive/web/tls/current/msg03416.html
        label = "test label";
        md = MessageDigest.getInstance("SHA-384");
        return doExpansion(md, secret, ByteArrayUtils.concatenate(label.getBytes(), seed), 148);

      default:
        LOGGER.severe("Unknwon label: " + labelId);
        return null;
      }
    } catch (NoSuchAlgorithmException e) {
      LOGGER.severe("Message digest algorithm not available.");
      e.printStackTrace();
      return null;
    }
  }

  /**
   * Performs the secret expansion as described in <a
   * href="http://tools.ietf.org/html/rfc5246#section-5">RFC 5246</a>.
   *
   * @param md
   *            the cryptographic hash function.
   * @param secret
   *            the secret.
   * @param data
   *            the data.
   * @param length
   *            the length of the expansion in <tt>bytes</tt>.
   * @return the expanded array with given length.
   */
  protected static byte[] doExpansion(MessageDigest md, byte[] secret, byte[] data, int length) {
    /*
     * P_hash(secret, seed) = HMAC_hash(secret, A(1) + seed) +
     * HMAC_hash(secret, A(2) + seed) + HMAC_hash(secret, A(3) + seed) + ...
     * where + indicates concatenation. A() is defined as: A(0) = seed, A(i)
     * = HMAC_hash(secret, A(i-1))
     */
    double hashLength = 32;
    if (md.getAlgorithm().equals("SHA-1")) {
      hashLength = 20;
    } else if (md.getAlgorithm().equals("SHA-384")) {
      hashLength = 48;
    }

    int iterations = (int) Math.ceil(length / hashLength);
    byte[] expansion = new byte[0];

    byte[] A = data;
    for (int i = 0; i < iterations; i++) {
      A = doHMAC(md, secret, A);
      expansion = ByteArrayUtils.concatenate(expansion, doHMAC(md, secret, ByteArrayUtils.concatenate(A, data)));
    }

    return ByteArrayUtils.truncate(expansion, length);
  }

  /**
   * Performs the HMAC computation as described in <a
   * href="http://tools.ietf.org/html/rfc2104#section-2">RFC 2104</a>.
   *
   * @param md
   *            the cryptographic hash function.
   * @param secret
   *            the secret key.
   * @param data
   *            the data.
   * @return the hash after HMAC has been applied.
   */
  public static byte[] doHMAC(MessageDigest md, byte[] secret, byte[] data) {
    // the block size of the hash function, always 64 bytes (for SHA-512 it
    // would be 128 bytes, but not needed right now, except for test
    // purpose)

    int B = 64;
    if (md.getAlgorithm().equals("SHA-512") || md.getAlgorithm().equals("SHA-384")) {
      B = 128;
    }

    // See http://tools.ietf.org/html/rfc2104#section-2
    // ipad = the byte 0x36 repeated B times
    byte[] ipad = new byte[B];
    Arrays.fill(ipad, (byte) 0x36);

    // opad = the byte 0x5C repeated B times
    byte[] opad = new byte[B];
    Arrays.fill(opad, (byte) 0x5C);

    /*
     * (1) append zeros to the end of K to create a B byte string (e.g., if
     * K is of length 20 bytes and B=64, then K will be appended with 44
     * zero bytes 0x00)
     */
    byte[] step1 = secret;
    if (secret.length < B) {
      // append zeros to the end of K to create a B byte string
      step1 = ByteArrayUtils.padArray(secret, (byte) 0x00, B);
    } else if (secret.length > B) {
      // Applications that use keys longer
      // than B bytes will first hash the key using H and then use the
      // resultant L byte string as the actual key to HMAC.
      md.update(secret);
      step1 = md.digest();
      md.reset();

      step1 = ByteArrayUtils.padArray(step1, (byte) 0x00, B);
    }

    /*
     * (2) XOR (bitwise exclusive-OR) the B byte string computed in step (1)
     * with ipad
     */
    byte[] step2 = ByteArrayUtils.xorArrays(step1, ipad);

    /*
     * (3) append the stream of data 'text' to the B byte string resulting
     * from step (2)
     */
    byte[] step3 = ByteArrayUtils.concatenate(step2, data);

    /*
     * (4) apply H to the stream generated in step (3)
     */
    md.update(step3);
    byte[] step4 = md.digest();
    md.reset();

    /*
     * (5) XOR (bitwise exclusive-OR) the B byte string computed in step (1)
     * with opad
     */
    byte[] step5 = ByteArrayUtils.xorArrays(step1, opad);

    /*
     * (6) append the H result from step (4) to the B byte string resulting
     * from step (5)
     */
    byte[] step6 = ByteArrayUtils.concatenate(step5, step4);

    /*
     * (7) apply H to the stream generated in step (6) and output the result
     */
    md.update(step6);
    byte[] step7 = md.digest();

    return step7;
  }

  protected void setCurrentReadState() {
    DTLSConnectionState connectionState;
    if (isClient) {
      connectionState = new DTLSConnectionState(cipherSuite, compressionMethod, serverWriteKey, serverWriteIV, serverWriteMACKey);
    } else {
      connectionState = new DTLSConnectionState(cipherSuite, compressionMethod, clientWriteKey, clientWriteIV, clientWriteMACKey);
    }
    session.setReadState(connectionState);
  }

  protected void setCurrentWriteState() {
    DTLSConnectionState connectionState;
    if (isClient) {
      connectionState = new DTLSConnectionState(cipherSuite, compressionMethod, clientWriteKey, clientWriteIV, clientWriteMACKey);
    } else {
      connectionState = new DTLSConnectionState(cipherSuite, compressionMethod, serverWriteKey, serverWriteIV, serverWriteMACKey);
    }
    session.setWriteState(connectionState);
  }

  /**
   * Wraps the message into (potentially multiple) record layers. Sets the
   * epoch, sequence number and handles fragmentation for handshake messages.
   *
   * @param fragment
   *            the {@link DTLSMessage} fragment.
   * @return the fragment wrapped into (multiple) record layers.
   */
  protected List<Record> wrapMessage(DTLSMessage fragment) {
   
    List<Record> records = new ArrayList<Record>();

    ContentType type = null;
    if (fragment instanceof ApplicationMessage) {
      type = ContentType.APPLICATION_DATA;
    } else if (fragment instanceof AlertMessage) {
      type = ContentType.ALERT;
    } else if (fragment instanceof ChangeCipherSpecMessage) {
      type = ContentType.CHANGE_CIPHER_SPEC;
    } else if (fragment instanceof HandshakeMessage) {
      type = ContentType.HANDSHAKE;
      HandshakeMessage handshakeMessage = (HandshakeMessage) fragment;
      setSequenceNumber(handshakeMessage);
     
      byte[] messageBytes = handshakeMessage.fragmentToByteArray();
     
      int maxFragmentLength = ScProperties.std.getInt("MAX_FRAGMENT_LENGTH");
      if (messageBytes.length > maxFragmentLength) {
        /*
         * The sender then creates N handshake messages, all with the
         * same message_seq value as the original handshake message.
         */
        int messageSeq = handshakeMessage.getMessageSeq();

        int numFragments = (messageBytes.length / maxFragmentLength) + 1;
       
        int offset = 0;
        for (int i = 0; i < numFragments; i++) {
          int fragmentLength = maxFragmentLength;
          if (offset + fragmentLength > messageBytes.length) {
            // the last fragment is normally shorter than the maximal size
            fragmentLength = messageBytes.length - offset;
          }
          byte[] fragmentBytes = new byte[fragmentLength];
          System.arraycopy(messageBytes, offset, fragmentBytes, 0, fragmentLength);
         
          FragmentedHandshakeMessage fragmentedMessage =
              new FragmentedHandshakeMessage(fragmentBytes, handshakeMessage.getMessageType(), offset, messageBytes.length);
         
          // all fragments have the same message_seq
          fragmentedMessage.setMessageSeq(messageSeq);
          offset += fragmentBytes.length;
         
          records.add(new Record(type, session.getWriteEpoch(), session.getSequenceNumber(), fragmentedMessage, session));
        }
      }
    }
   
    if (records.isEmpty()) { // no fragmentation needed
      records.add(new Record(type, session.getWriteEpoch(), session.getSequenceNumber(), fragment, session));
    }
   
    return records;
  }

  /**
   * Determines, using the epoch and sequence number, whether this record is
   * the next one which needs to be processed by the handshake protocol.
   *
   * @param record
   *            the current received message.
   * @return <tt>true</tt> if the current message is the next to process,
   *         <tt>false</tt> otherwise.
   * @throws HandshakeException
   */
  protected boolean processMessageNext(Record record) throws HandshakeException {

    int epoch = record.getEpoch();
    if (epoch < session.getReadEpoch()) {
      // discard old message
      LOGGER.info("Discarded message from " + endpointAddress.toString() + " due to older epoch.");
      return false;
    } else if (epoch == session.getReadEpoch()) {
      DTLSMessage fragment = record.getFragment();
      if (fragment instanceof AlertMessage) {
        return true; // Alerts must be processed immediately
      } else if (fragment instanceof ChangeCipherSpecMessage) {
        return true; // CCS must be processed immediately
      } else if (fragment instanceof HandshakeMessage) {
        int messageSeq = ((HandshakeMessage) fragment).getMessageSeq();

        if (messageSeq == nextReceiveSeq) {
          if (!(fragment instanceof FragmentedHandshakeMessage)) {
            // each fragment has the same message_seq, therefore
            // don't increment yet
            incrementNextReceiveSeq();
          }
          return true;
        } else if (messageSeq > nextReceiveSeq) {
          LOGGER.info("Queued newer message from same epoch, message_seq: " + messageSeq + ", next_receive_seq: " + nextReceiveSeq);
          queuedMessages.add(record);
          return false;
        } else {
          LOGGER.info("Discarded message due to older message_seq: " + messageSeq + ", next_receive_seq: " + nextReceiveSeq);
          return false;
        }
      } else {
        return false;
      }
    } else {
      // newer epoch, queue message
      queuedMessages.add(record);
      return false;
    }
  }
 
  /**
   * Loads the given keyStore (location specified in Californium.properties).
   * The keyStore must contain the private key and the corresponding
   * certificate (chain). The keyStore alias is expected to be "client".
   */
  protected abstract void loadKeyStore();
 
  /**
   * Loads the trusted certificates.
   *
   * @return the trusted certificates.
   */
  protected Certificate[] loadTrustedCertificates() {
    Certificate[] trustedCertificates = new Certificate[1];

    try {
      KeyStore trustStore = KeyStore.getInstance("JKS");
      InputStream in = new FileInputStream(DTLSConnector.TRUST_STORE_LOCATION);
      trustStore.load(in, TRUST_STORE_PASSWORD.toCharArray());
     
      // TODO load multiple certificates?
      trustedCertificates[0] = trustStore.getCertificate("root");
    } catch (Exception e) {
      LOGGER.severe("Could not load the trusted certificates.");
      e.printStackTrace();
    }

    return trustedCertificates;
  }
 
  /**
   * Called when a fragmented handshake message is received. Checks if all
   * fragments already here to reassemble the handshake message and if so,
   * returns the whole handshake message.
   *
   * @param fragment
   *            the fragmented handshake message.
   * @return the reassembled handshake message (if all fragements available),
   *         <code>null</code> otherwise.
   * @throws HandshakeException
   */
  protected HandshakeMessage handleFragmentation(FragmentedHandshakeMessage fragment) throws HandshakeException {
    HandshakeMessage reassembledMessage = null;
   
    int messageSeq = fragment.getMessageSeq();
    if (fragmentedMessages.get(messageSeq) == null) {
      fragmentedMessages.put(messageSeq, new ArrayList<FragmentedHandshakeMessage>());
    }
    // store fragment together with other fragments of same message_seq
    fragmentedMessages.get(messageSeq).add(fragment);
   
    reassembledMessage = reassembleFragments(messageSeq, fragment.getMessageLength(), fragment.getMessageType(), session);
    if (reassembledMessage != null) {
      // message could be reassembled, therefore increase the next_receive_seq
      incrementNextReceiveSeq();
      fragmentedMessages.remove(messageSeq);
    }
   
    return reassembledMessage;
  }
 
  /**
   * Tries to reassemble the handshake message with the available fragments.
   *
   * @param messageSeq
   *            the fragment's message_seq
   * @param totalLength
   *            the expected total length of the reassembled fragment
   * @param type
   *            the type of the handshake message
   * @param session
   *            the {@link DTLSSession}
   * @return the reassembled handshake message (if all fragements available),
   *         <code>null</code> otherwise.
   * @throws HandshakeException
   */
  protected HandshakeMessage reassembleFragments(int messageSeq, int totalLength, HandshakeType type, DTLSSession session) throws HandshakeException {
    List<FragmentedHandshakeMessage> fragments = fragmentedMessages.get(messageSeq);
    HandshakeMessage message = null;

    // sort according to fragment offset
    Collections.sort(fragments, new Comparator<FragmentedHandshakeMessage>() {

      // @Override
      public int compare(FragmentedHandshakeMessage o1, FragmentedHandshakeMessage o2) {
        if (o1.getFragmentOffset() == o2.getFragmentOffset()) {
          return 0;
        } else if (o1.getFragmentOffset() < o2.getFragmentOffset()) {
          return -1;
        } else {
          return 1;
        }
      }
    });

    byte[] reassembly = new byte[] {};
    int offset = 0;
    for (FragmentedHandshakeMessage fragmentedHandshakeMessage : fragments) {
     
      int fragmentOffset = fragmentedHandshakeMessage.getFragmentOffset();
      int fragmentLength = fragmentedHandshakeMessage.getFragmentLength();
     
      if (fragmentOffset == offset) { // eliminate duplicates
        // case: no overlap
        reassembly = ByteArrayUtils.concatenate(reassembly, fragmentedHandshakeMessage.fragmentToByteArray());
        offset = reassembly.length;
      } else if (fragmentOffset < offset && (fragmentOffset + fragmentLength) > offset) {
        // case: overlap fragment
       
        // determine the offset where the fragment adds new information for the reassembly
        int newOffset = offset - fragmentOffset;
        int newLength = fragmentLength - newOffset;
        byte[] newBytes = new byte[newLength];
        // take only the new bytes and add them
        System.arraycopy(fragmentedHandshakeMessage.fragmentToByteArray(), newOffset, newBytes, 0, newLength)
        reassembly = ByteArrayUtils.concatenate(reassembly, newBytes);
       
        offset = reassembly.length;
      }
    }
   
    if (reassembly.length == totalLength) {
      // the reassembled fragment has the expected length
      FragmentedHandshakeMessage wholeMessage = new FragmentedHandshakeMessage(type, totalLength, messageSeq, 0, reassembly);
      reassembly = wholeMessage.toByteArray();
     
      KeyExchangeAlgorithm keyExchangeAlgorithm = KeyExchangeAlgorithm.NULL;
      boolean receiveRawPublicKey = false;
      if (session != null) {
        keyExchangeAlgorithm = session.getKeyExchange();
        receiveRawPublicKey = session.receiveRawPublicKey();
      }
      message = HandshakeMessage.fromByteArray(reassembly, keyExchangeAlgorithm, receiveRawPublicKey);
    }
   
    return message;
  }

  // Getters and Setters ////////////////////////////////////////////

  public CipherSuite getCipherSuite() {
    return cipherSuite;
  }

  /**
   * Sets the negotiated {@link CipherSuite} and the corresponding
   * {@link KeyExchangeAlgorithm}.
   *
   * @param cipherSuite
   *            the cipher suite.
   */
  public void setCipherSuite(CipherSuite cipherSuite) {
    this.cipherSuite = cipherSuite;
    this.keyExchange = cipherSuite.getKeyExchange();
    this.session.setKeyExchange(keyExchange);
    this.session.setCipherSuite(cipherSuite);
  }

  public byte[] getMasterSecret() {
    return masterSecret;
  }

  public SecretKey getClientWriteMACKey() {
    return clientWriteMACKey;
  }

  public SecretKey getServerWriteMACKey() {
    return serverWriteMACKey;
  }

  public IvParameterSpec getClientWriteIV() {
    return clientWriteIV;
  }

  public IvParameterSpec getServerWriteIV() {
    return serverWriteIV;
  }

  public SecretKey getClientWriteKey() {
    return clientWriteKey;
  }

  public SecretKey getServerWriteKey() {
    return serverWriteKey;
  }

  public DTLSSession getSession() {
    return session;
  }

  public void setSession(DTLSSession session) {
    this.session = session;
  }

  /**
   * Add the smallest available message sequence to the handshake message.
   *
   * @param message
   *            the {@link HandshakeMessage}.
   */
  public void setSequenceNumber(HandshakeMessage message) {
    message.setMessageSeq(sequenceNumber);
    sequenceNumber++;
  }

  public RawData getMessage() {
    return message;
  }

  public void setMessage(RawData message) {
    this.message = message;
  }

  public int getNextReceiveSeq() {
    return nextReceiveSeq;
  }

  public void incrementNextReceiveSeq() {
    this.nextReceiveSeq++;
  }

  public CompressionMethod getCompressionMethod() {
    return compressionMethod;
  }

  public void setCompressionMethod(CompressionMethod compressionMethod) {
    this.compressionMethod = compressionMethod;
    this.session.setCompressionMethod(compressionMethod);
  }
}
TOP

Related Classes of ch.ethz.inf.vs.scandium.dtls.Handshaker

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.