public byte[] calculateDtlsMac(
      ProtocolVersion protocolVersion,
      ProtocolMessageType contentType,
      byte[] data,
      long dtlsSequenceNumber,
      int epochNumber) {

    byte[] SQN =
        ArrayConverter.concatenate(
            ArrayConverter.intToBytes(epochNumber, 2),
            ArrayConverter.longToUint48Bytes(dtlsSequenceNumber));
    byte[] HDR =
        ArrayConverter.concatenate(
            contentType.getArrayValue(),
            protocolVersion.getValue(),
            ArrayConverter.intToBytes(data.length, 2));

    writeMac.update(SQN);
    writeMac.update(HDR);
    writeMac.update(data);

    if (LOGGER.isDebugEnabled()) {
      LOGGER.debug(
          "The MAC will be caluculated over the following data: {}",
          ArrayConverter.bytesToHexString(
              ArrayConverter.concatenate(
                  ArrayConverter.intToBytes(epochNumber, 2),
                  ArrayConverter.longToUint48Bytes(sequenceNumber),
                  HDR,
                  data)));
    }

    byte[] result = writeMac.doFinal();

    if (LOGGER.isDebugEnabled()) {
      LOGGER.debug("MAC result: {}", ArrayConverter.bytesToHexString(result));
    }

    return result;
  }
  /**
   * From the Lucky13 paper: An individual record R (viewed as a byte sequence of length at least
   * zero) is processed as follows. The sender maintains an 8-byte sequence number SQN which is
   * incremented for each record sent, and forms a 5-byte field HDR consisting of a 1-byte type
   * field, a 2-byte version field, and a 2-byte length field. It then calculates a MAC over the
   * bytes SQN || HDR || R.
   *
   * @param protocolVersion
   * @param contentType
   * @param data
   * @return
   */
  public byte[] calculateMac(
      ProtocolVersion protocolVersion, ProtocolMessageType contentType, byte[] data) {

    byte[] SQN = ArrayConverter.longToUint64Bytes(sequenceNumber);
    byte[] HDR =
        ArrayConverter.concatenate(
            contentType.getArrayValue(),
            protocolVersion.getValue(),
            ArrayConverter.intToBytes(data.length, 2));

    writeMac.update(SQN);
    writeMac.update(HDR);
    writeMac.update(data);

    LOGGER.debug(
        "The MAC was caluculated over the following data: {}",
        ArrayConverter.bytesToHexString(ArrayConverter.concatenate(SQN, HDR, data)));

    byte[] result = writeMac.doFinal();

    LOGGER.debug("MAC result: {}", ArrayConverter.bytesToHexString(result));

    // we increment sequence number for the sent records
    sequenceNumber++;

    return result;
  }
 /**
  * Takes correctly padded data and encrypts it
  *
  * @param data correctly padded data
  * @return
  * @throws CryptoException
  */
 public byte[] encrypt(byte[] data) throws CryptoException {
   try {
     byte[] ciphertext;
     if (useExplicitIv) {
       ciphertext = ArrayConverter.concatenate(encryptIv.getIV(), encryptCipher.doFinal(data));
     } else {
       encryptCipher.init(Cipher.ENCRYPT_MODE, encryptKey, encryptIv);
       ciphertext = encryptCipher.doFinal(data);
       encryptIv =
           new IvParameterSpec(
               Arrays.copyOfRange(
                   ciphertext,
                   ciphertext.length - decryptCipher.getBlockSize(),
                   ciphertext.length));
     }
     return ciphertext;
   } catch (BadPaddingException
       | IllegalBlockSizeException
       | InvalidAlgorithmParameterException
       | InvalidKeyException ex) {
     throw new CryptoException(ex);
   }
 }
  public TlsRecordBlockCipher(TlsContext tlsContext)
      throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException,
          InvalidAlgorithmParameterException {
    this.tlsContext = tlsContext;
    ProtocolVersion protocolVersion = tlsContext.getProtocolVersion();
    CipherSuite cipherSuite = tlsContext.getSelectedCipherSuite();
    if (protocolVersion == ProtocolVersion.TLS11
        || protocolVersion == ProtocolVersion.TLS12
        || protocolVersion == ProtocolVersion.DTLS10
        || protocolVersion == ProtocolVersion.DTLS12) {
      useExplicitIv = true;
    }
    bulkCipherAlg = BulkCipherAlgorithm.getBulkCipherAlgorithm(cipherSuite);
    CipherAlgorithm cipherAlg = AlgorithmResolver.getCipher(cipherSuite);
    int keySize = cipherAlg.getKeySize();
    encryptCipher = Cipher.getInstance(cipherAlg.getJavaName());
    decryptCipher = Cipher.getInstance(cipherAlg.getJavaName());

    MacAlgorithm macAlg = AlgorithmResolver.getMacAlgorithm(cipherSuite);
    readMac = Mac.getInstance(macAlg.getJavaName());
    writeMac = Mac.getInstance(macAlg.getJavaName());

    int secretSetSize = (2 * keySize) + readMac.getMacLength() + writeMac.getMacLength();

    if (!useExplicitIv) {
      secretSetSize += encryptCipher.getBlockSize() + decryptCipher.getBlockSize();
    }

    byte[] masterSecret = tlsContext.getMasterSecret();
    byte[] seed = tlsContext.getServerClientRandom();

    PRFAlgorithm prfAlgorithm =
        AlgorithmResolver.getPRFAlgorithm(
            tlsContext.getProtocolVersion(), tlsContext.getSelectedCipherSuite());
    byte[] keyBlock =
        PseudoRandomFunction.compute(
            prfAlgorithm,
            masterSecret,
            PseudoRandomFunction.KEY_EXPANSION_LABEL,
            seed,
            secretSetSize);

    LOGGER.debug("A new key block was generated: {}", ArrayConverter.bytesToHexString(keyBlock));

    int offset = 0;
    byte[] clientMacWriteSecret =
        Arrays.copyOfRange(keyBlock, offset, offset + readMac.getMacLength());
    offset += readMac.getMacLength();
    LOGGER.debug(
        "Client MAC write Secret: {}", ArrayConverter.bytesToHexString(clientMacWriteSecret));

    byte[] serverMacWriteSecret =
        Arrays.copyOfRange(keyBlock, offset, offset + writeMac.getMacLength());
    offset += writeMac.getMacLength();
    LOGGER.debug(
        "Server MAC write Secret:  {}", ArrayConverter.bytesToHexString(serverMacWriteSecret));

    clientWriteKey = Arrays.copyOfRange(keyBlock, offset, offset + keySize);
    offset += keySize;
    LOGGER.debug("Client write key: {}", ArrayConverter.bytesToHexString(clientWriteKey));

    serverWriteKey = Arrays.copyOfRange(keyBlock, offset, offset + keySize);
    offset += keySize;
    LOGGER.debug("Server write key: {}", ArrayConverter.bytesToHexString(serverWriteKey));

    byte[] clientWriteIv, serverWriteIv;
    if (useExplicitIv) {
      clientWriteIv = new byte[encryptCipher.getBlockSize()];
      RandomHelper.getRandom().nextBytes(clientWriteIv);
      serverWriteIv = new byte[decryptCipher.getBlockSize()];
      RandomHelper.getRandom().nextBytes(serverWriteIv);
    } else {
      clientWriteIv = Arrays.copyOfRange(keyBlock, offset, offset + encryptCipher.getBlockSize());
      offset += encryptCipher.getBlockSize();
      LOGGER.debug("Client write IV: {}", ArrayConverter.bytesToHexString(clientWriteIv));
      serverWriteIv = Arrays.copyOfRange(keyBlock, offset, offset + decryptCipher.getBlockSize());
      offset += decryptCipher.getBlockSize();
      LOGGER.debug("Server write IV: {}", ArrayConverter.bytesToHexString(serverWriteIv));
    }

    if (tlsContext.getMyConnectionEnd() == ConnectionEnd.CLIENT) {
      encryptIv = new IvParameterSpec(clientWriteIv);
      decryptIv = new IvParameterSpec(serverWriteIv);
      encryptKey = new SecretKeySpec(clientWriteKey, bulkCipherAlg.getJavaName());
      decryptKey = new SecretKeySpec(serverWriteKey, bulkCipherAlg.getJavaName());
      encryptCipher.init(Cipher.ENCRYPT_MODE, encryptKey, encryptIv);
      decryptCipher.init(Cipher.DECRYPT_MODE, decryptKey, decryptIv);
      readMac.init(new SecretKeySpec(serverMacWriteSecret, macAlg.getJavaName()));
      writeMac.init(new SecretKeySpec(clientMacWriteSecret, macAlg.getJavaName()));
    } else {
      decryptIv = new IvParameterSpec(clientWriteIv);
      encryptIv = new IvParameterSpec(serverWriteIv);
      // todo check if this correct???
      encryptCipher.init(
          Cipher.ENCRYPT_MODE,
          new SecretKeySpec(serverWriteKey, bulkCipherAlg.getJavaName()),
          encryptIv);
      decryptCipher.init(
          Cipher.DECRYPT_MODE,
          new SecretKeySpec(clientWriteKey, bulkCipherAlg.getJavaName()),
          decryptIv);
      readMac.init(new SecretKeySpec(clientMacWriteSecret, macAlg.getJavaName()));
      writeMac.init(new SecretKeySpec(serverMacWriteSecret, macAlg.getJavaName()));
    }

    if (offset != keyBlock.length) {
      throw new CryptoException("Offset exceeded the generated key block length");
    }

    // mac has to be put into one or more blocks, depending on the MAC/block
    // length
    // additionally, there is a need for one explicit IV block
    minimalEncryptedRecordLength =
        ((readMac.getMacLength() / decryptCipher.getBlockSize()) + 2)
            * decryptCipher.getBlockSize();
  }
  /**
   * @param message
   * @param pointer
   * @return
   */
  @Override
  public int parseMessageAction(byte[] message, int pointer) {
    if (message[pointer] != HandshakeMessageType.SERVER_KEY_EXCHANGE.getValue()) {
      throw new InvalidMessageTypeException(HandshakeMessageType.SERVER_KEY_EXCHANGE);
    }
    protocolMessage.setType(message[pointer]);

    int currentPointer = pointer + HandshakeByteLength.MESSAGE_TYPE;
    int nextPointer = currentPointer + HandshakeByteLength.MESSAGE_TYPE_LENGTH;
    int length =
        ArrayConverter.bytesToInt(Arrays.copyOfRange(message, currentPointer, nextPointer));
    protocolMessage.setLength(length);

    currentPointer = nextPointer;
    nextPointer = currentPointer + HandshakeByteLength.DH_PARAM_LENGTH;
    int pLength =
        ArrayConverter.bytesToInt(Arrays.copyOfRange(message, currentPointer, nextPointer));
    protocolMessage.setpLength(pLength);

    currentPointer = nextPointer;
    nextPointer = currentPointer + protocolMessage.getpLength().getValue();
    BigInteger p = new BigInteger(1, Arrays.copyOfRange(message, currentPointer, nextPointer));
    protocolMessage.setP(p);

    currentPointer = nextPointer;
    nextPointer = currentPointer + HandshakeByteLength.DH_PARAM_LENGTH;
    int gLength =
        ArrayConverter.bytesToInt(Arrays.copyOfRange(message, currentPointer, nextPointer));
    protocolMessage.setgLength(gLength);

    currentPointer = nextPointer;
    nextPointer = currentPointer + protocolMessage.getgLength().getValue();
    BigInteger g = new BigInteger(1, Arrays.copyOfRange(message, currentPointer, nextPointer));
    protocolMessage.setG(g);

    currentPointer = nextPointer;
    nextPointer = currentPointer + HandshakeByteLength.DH_PARAM_LENGTH;
    int publicKeyLength =
        ArrayConverter.bytesToInt(Arrays.copyOfRange(message, currentPointer, nextPointer));
    protocolMessage.setPublicKeyLength(publicKeyLength);

    currentPointer = nextPointer;
    nextPointer = currentPointer + protocolMessage.getPublicKeyLength().getValue();
    BigInteger publicKey =
        new BigInteger(1, Arrays.copyOfRange(message, currentPointer, nextPointer));
    protocolMessage.setPublicKey(publicKey);

    byte[] dhParams =
        ArrayConverter.concatenate(
            ArrayConverter.intToBytes(
                protocolMessage.getpLength().getValue(), HandshakeByteLength.DH_PARAM_LENGTH),
            BigIntegers.asUnsignedByteArray(protocolMessage.getP().getValue()),
            ArrayConverter.intToBytes(
                protocolMessage.getgLength().getValue(), HandshakeByteLength.DH_PARAM_LENGTH),
            BigIntegers.asUnsignedByteArray(protocolMessage.getG().getValue()),
            ArrayConverter.intToBytes(
                protocolMessage.getPublicKeyLength().getValue(),
                HandshakeByteLength.DH_PARAM_LENGTH),
            BigIntegers.asUnsignedByteArray(protocolMessage.getPublicKey().getValue()));
    InputStream is = new ByteArrayInputStream(dhParams);

    try {
      ServerDHParams publicKeyParameters = ServerDHParams.parse(is);

      tlsContext.setServerDHParameters(publicKeyParameters);

      if (tlsContext.getProtocolVersion() == ProtocolVersion.DTLS12
          || tlsContext.getProtocolVersion() == ProtocolVersion.TLS12) {
        currentPointer = nextPointer;
        nextPointer++;
        HashAlgorithm ha = HashAlgorithm.getHashAlgorithm(message[currentPointer]);
        protocolMessage.setHashAlgorithm(ha.getValue());

        currentPointer = nextPointer;
        nextPointer++;
        SignatureAlgorithm sa = SignatureAlgorithm.getSignatureAlgorithm(message[currentPointer]);
        protocolMessage.setSignatureAlgorithm(sa.getValue());
      }
      currentPointer = nextPointer;
      nextPointer = currentPointer + HandshakeByteLength.SIGNATURE_LENGTH;
      int signatureLength =
          ArrayConverter.bytesToInt(Arrays.copyOfRange(message, currentPointer, nextPointer));
      protocolMessage.setSignatureLength(signatureLength);

      currentPointer = nextPointer;
      nextPointer = currentPointer + signatureLength;
      protocolMessage.setSignature(Arrays.copyOfRange(message, currentPointer, nextPointer));

      protocolMessage.setCompleteResultingMessage(
          Arrays.copyOfRange(message, pointer, nextPointer));

      return nextPointer;
    } catch (IOException ex) {
      throw new WorkflowExecutionException("DH public key parsing failed", ex);
    }
  }
  @Override
  public byte[] prepareMessageAction() {
    // To use true DH ephemeral we need to precompute the prime number P(DH modulus)
    /**
     * int defaultPrimeProbability = 30;
     *
     * <p>DHParametersGenerator generator = new DHParametersGenerator(); //Genration of a higher bit
     * prime number takes too long (512 bits takes 2 seconds) generator.init(512,
     * defaultPrimeProbability, new SecureRandom()); DHParameters params =
     * generator.generateParameters();
     */
    DHPublicKeyParameters dhPublic;

    // fixed DH modulus P and DH generator G
    byte[] pArray =
        ArrayConverter.hexStringToByteArray(
            "ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc"
                + "74020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f14374fe1356d6d"
                + "51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7edee386bfb5a899fa5ae9f24"
                + "117c4b1fe649286651ece45b3dc2007cb8a163bf0598da48361c55d39a69163fa8fd24cf5f83"
                + "655d23dca3ad961c62f356208552bb9ed529077096966d670c354e4abc9804f1746c08ca1821"
                + "7c32905e462e36ce3be39e772c180e86039b2783a2ec07a28fb5c55df06f4c52c9de2bcbf695"
                + "5817183995497cea956ae515d2261898fa051015728e5a8aacaa68ffffffffffffffff");
    byte[] gArray = {0x02};
    BigInteger p = new BigInteger(1, pArray);
    BigInteger g = new BigInteger(1, gArray);
    DHParameters params = new DHParameters(p, g);

    KeyGenerationParameters kgp = new DHKeyGenerationParameters(new SecureRandom(), params);
    DHKeyPairGenerator keyGen = new DHKeyPairGenerator();
    keyGen.init(kgp);
    AsymmetricCipherKeyPair serverKeyPair = keyGen.generateKeyPair();

    dhPublic = (DHPublicKeyParameters) serverKeyPair.getPublic();
    DHPrivateKeyParameters dhPrivate = (DHPrivateKeyParameters) serverKeyPair.getPrivate();

    protocolMessage.setG(dhPublic.getParameters().getG());
    protocolMessage.setP(dhPublic.getParameters().getP());
    protocolMessage.setPublicKey(dhPublic.getY());
    protocolMessage.setPrivateKey(dhPrivate.getX());
    tlsContext.setServerDHPrivateKeyParameters(dhPrivate);

    byte[] serializedP = BigIntegers.asUnsignedByteArray(protocolMessage.getP().getValue());
    protocolMessage.setSerializedP(serializedP);
    protocolMessage.setSerializedPLength(serializedP.length);

    byte[] serializedG = BigIntegers.asUnsignedByteArray(protocolMessage.getG().getValue());
    protocolMessage.setSerializedG(serializedG);
    protocolMessage.setSerializedGLength(serializedG.length);

    byte[] serializedPublicKey =
        BigIntegers.asUnsignedByteArray(protocolMessage.getPublicKey().getValue());
    protocolMessage.setSerializedPublicKey(serializedPublicKey);
    protocolMessage.setSerializedPublicKeyLength(serializedPublicKey.length);

    byte[] dhParams =
        ArrayConverter.concatenate(
            ArrayConverter.intToBytes(
                protocolMessage.getSerializedPLength().getValue(),
                HandshakeByteLength.DH_PARAM_LENGTH),
            protocolMessage.getSerializedP().getValue(),
            ArrayConverter.intToBytes(
                protocolMessage.getSerializedGLength().getValue(),
                HandshakeByteLength.DH_PARAM_LENGTH),
            protocolMessage.getSerializedG().getValue(),
            ArrayConverter.intToBytes(
                protocolMessage.getSerializedPublicKeyLength().getValue(),
                HandshakeByteLength.DH_PARAM_LENGTH),
            protocolMessage.getSerializedPublicKey().getValue());
    InputStream is = new ByteArrayInputStream(dhParams);

    try {
      ServerDHParams publicKeyParameters = ServerDHParams.parse(is);

      tlsContext.setServerDHParameters(publicKeyParameters);

      KeyStore ks = tlsContext.getKeyStore();

      // could be extended to choose the algorithms depending on the certificate
      SignatureAndHashAlgorithm selectedSignatureHashAlgo =
          new SignatureAndHashAlgorithm(SignatureAlgorithm.RSA, HashAlgorithm.SHA1);
      protocolMessage.setSignatureAlgorithm(
          selectedSignatureHashAlgo.getSignatureAlgorithm().getValue());
      protocolMessage.setHashAlgorithm(selectedSignatureHashAlgo.getHashAlgorithm().getValue());

      Key key = ks.getKey(tlsContext.getAlias(), tlsContext.getPassword().toCharArray());

      RSAPrivateCrtKey rsaKey = (RSAPrivateCrtKey) key;

      Signature instance = Signature.getInstance(selectedSignatureHashAlgo.getJavaName());
      instance.initSign(rsaKey);
      LOGGER.debug(
          "SignatureAndHashAlgorithm for ServerKeyExchange message: {}",
          selectedSignatureHashAlgo.getJavaName());

      byte[] toBeSignedBytes =
          ArrayConverter.concatenate(
              tlsContext.getClientRandom(), tlsContext.getServerRandom(), dhParams);

      instance.update(toBeSignedBytes);
      byte[] signature = instance.sign();
      protocolMessage.setSignature(signature);
      protocolMessage.setSignatureLength(signature.length);

      byte[] result =
          ArrayConverter.concatenate(
              dhParams,
              new byte[] {
                protocolMessage.getHashAlgorithm().getValue(),
                protocolMessage.getSignatureAlgorithm().getValue()
              },
              ArrayConverter.intToBytes(
                  protocolMessage.getSignatureLength().getValue(),
                  HandshakeByteLength.SIGNATURE_LENGTH),
              protocolMessage.getSignature().getValue());

      protocolMessage.setLength(result.length);

      long header =
          (HandshakeMessageType.SERVER_KEY_EXCHANGE.getValue() << 24)
              + protocolMessage.getLength().getValue();

      protocolMessage.setCompleteResultingMessage(
          ArrayConverter.concatenate(ArrayConverter.longToUint32Bytes(header), result));

    } catch (KeyStoreException
        | NoSuchAlgorithmException
        | UnrecoverableKeyException
        | InvalidKeyException
        | SignatureException
        | IOException ex) {
      throw new ConfigurationException(ex.getLocalizedMessage(), ex);
    }

    return protocolMessage.getCompleteResultingMessage().getValue();
  }
  @Override
  public byte[] getFrame() {

    return ArrayConverter.concatenate(eapframe.getFrame(), frame);
  }