/**
   ssldecode.c


   Copyright (C) 1999-2000 RTFM, Inc.
   All Rights Reserved

   This package is a SSLv3/TLS protocol analyzer written by Eric Rescorla
   <ekr@rtfm.com> and licensed by RTFM, Inc.

   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. All advertising materials mentioning features or use of this software
      must display the following acknowledgement:

      This product includes software developed by Eric Rescorla for
      RTFM, Inc.

   4. Neither the name of RTFM, Inc. nor the name of Eric Rescorla may be
      used to endorse or promote products derived from this
      software without specific prior written permission.

   THIS SOFTWARE IS PROVIDED BY ERIC RESCORLA AND RTFM, INC. ``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 REGENTS 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 SUCH
   DAMAGE.

   $Id: ssldecode.c,v 1.9 2002/08/17 01:33:17 ekr Exp $


   ekr@rtfm.com  Thu Apr  1 09:54:53 1999
 */

#include "network.h"
#include "ssl_h.h"
#include "sslprint.h"
#include "ssl.enums.h"
#ifdef OPENSSL
#include <openssl/err.h>
#include <openssl/kdf.h>
#include <openssl/ssl.h>
#include <openssl/hmac.h>
#include <openssl/evp.h>
#include <openssl/md5.h>
#include <openssl/x509v3.h>
#endif
#include "ssldecode.h"
#include "ssl_rec.h"
#include "r_assoc.h"

#define PRF(ssl, secret, usage, rnd1, rnd2, out)              \
  (ssl->version == SSLV3_VERSION)                             \
      ? ssl3_prf(ssl, secret, usage, rnd1, rnd2, out)         \
      : ((ssl->version == TLSV12_VERSION)                     \
             ? tls12_prf(ssl, secret, usage, rnd1, rnd2, out) \
             : tls_prf(ssl, secret, usage, rnd1, rnd2, out))

static char *ssl_password;

extern char *digests[];
extern UINT4 SSL_print_flags;

struct ssl_decode_ctx_ {
#ifdef OPENSSL
  SSL_CTX *ssl_ctx;
  SSL *ssl;
  r_assoc *session_cache;
  FILE *ssl_key_log_file;
#else
  char dummy; /* Some compilers (Win32) don't like empty
                  structs */
#endif
};

struct ssl_decoder_ {
  ssl_decode_ctx *ctx;
  Data *session_id;
  SSL_CipherSuite *cs;
  Data *client_random;
  Data *server_random;
  int ephemeral_rsa;
  Data *PMS;
  Data *MS;
  Data *SHTS;  // Server Handshake traffic secret
  Data *CHTS;  // Client Handshake traffic secret
  Data *STS;   // Server traffic Secret
  Data *CTS;   // Client traffic secret
  Data *handshake_messages;
  Data *session_hash;
  ssl_rec_decoder *c_to_s;
  ssl_rec_decoder *s_to_c;
  ssl_rec_decoder *c_to_s_n;
  ssl_rec_decoder *s_to_c_n;
};

#ifdef OPENSSL
static int tls_P_hash PROTO_LIST(
    (ssl_obj * ssl, Data *secret, Data *seed, const EVP_MD *md, Data *out));
static int tls12_prf PROTO_LIST((ssl_obj * ssl,
                                 Data *secret,
                                 char *usage,
                                 Data *rnd1,
                                 Data *rnd2,
                                 Data *out));
static int tls_prf PROTO_LIST((ssl_obj * ssl,
                               Data *secret,
                               char *usage,
                               Data *rnd1,
                               Data *rnd2,
                               Data *out));
static int ssl3_prf PROTO_LIST((ssl_obj * ssl,
                                Data *secret,
                                char *usage,
                                Data *rnd1,
                                Data *rnd2,
                                Data *out));
static int ssl3_generate_export_iv
    PROTO_LIST((ssl_obj * ssl, Data *rnd1, Data *rnd2, Data *out));
static int ssl_generate_keying_material PROTO_LIST((ssl_obj * ssl,
                                                    ssl_decoder *d));
static int ssl_generate_session_hash PROTO_LIST((ssl_obj * ssl,
                                                 ssl_decoder *d));
static int ssl_read_key_log_file PROTO_LIST((ssl_obj * obj, ssl_decoder *d));
#endif

static int ssl_create_session_lookup_key PROTO_LIST(
    (ssl_obj * ssl, UCHAR *id, UINT4 idlen, UCHAR **keyp, UINT4 *keyl));
int ssl_save_session PROTO_LIST((ssl_obj * ssl, ssl_decoder *d));
int ssl_restore_session PROTO_LIST((ssl_obj * ssl, ssl_decoder *d));

/*The password code is not thread safe*/
static int password_cb(char *buf, int num, int rwflag, void *userdata) {
  if(num < strlen(ssl_password) + 1)
    return 0;

  strcpy(buf, ssl_password);
  return strlen(ssl_password);
}

int ssl_decode_ctx_create(ssl_decode_ctx **dp,
                          char *keyfile,
                          char *pass,
                          char *keylogfile) {
#ifdef OPENSSL
  ssl_decode_ctx *d = 0;
  int _status;

  SSL_library_init();
  OpenSSL_add_all_algorithms();
  if(!(d = (ssl_decode_ctx *)malloc(sizeof(ssl_decode_ctx))))
    ABORT(R_NO_MEMORY);
  if(!(d->ssl_ctx = SSL_CTX_new(SSLv23_server_method())))
    ABORT(R_NO_MEMORY);
  if(keyfile) {
    if(pass) {
      ssl_password = pass;
      SSL_CTX_set_default_passwd_cb(d->ssl_ctx, password_cb);
    }
#if 0      
      if(SSL_CTX_use_certificate_file(d->ssl_ctx,keyfile,SSL_FILETYPE_PEM)!=1){
        fprintf(stderr,"Problem loading certificate file\n");
        ABORT(R_INTERNAL);
      }
#endif
    if(SSL_CTX_use_PrivateKey_file(d->ssl_ctx, keyfile, SSL_FILETYPE_PEM) !=
       1) {
      fprintf(stderr, "Problem loading private key\n");
      ABORT(R_INTERNAL);
    }
  }
  if(!(d->ssl = SSL_new(d->ssl_ctx)))
    ABORT(R_NO_MEMORY);

  if(r_assoc_create(&d->session_cache))
    ABORT(R_NO_MEMORY);

  if(keylogfile) {
    if(!(d->ssl_key_log_file = fopen(keylogfile, "r"))) {
      fprintf(stderr, "Failed to open ssl key log file");
      ABORT(R_INTERNAL);
    }
  } else {
    d->ssl_key_log_file = NULL;
  }

  X509V3_add_standard_extensions();

  *dp = d;
  _status = 0;
abort:
  return _status;
#else
  return 0;
#endif
}

int ssl_decode_ctx_destroy(ssl_decode_ctx **dp) {
#ifdef OPENSSL
  ssl_decode_ctx *d = *dp;
  if(!d)
    return 0;
  if(d->ssl_key_log_file) {
    fclose(d->ssl_key_log_file);
  }

  r_assoc_destroy(&d->session_cache);

  SSL_CTX_free(d->ssl_ctx);
  SSL_free(d->ssl);
  free(d);
#endif
  return 0;
}

int ssl_decoder_create(ssl_decoder **dp, ssl_decode_ctx *ctx) {
  int _status;

  ssl_decoder *d = 0;

#ifdef OPENSSL
  if(!(d = (ssl_decoder *)calloc(1, sizeof(ssl_decoder))))
    ABORT(R_NO_MEMORY);
  d->ctx = ctx;

  *dp = d;
  _status = 0;
abort:
  if(_status)
    ssl_decoder_destroy(&d);
  return _status;
#else
  return 0;
#endif
}

int ssl_decoder_destroy(ssl_decoder **dp) {
#ifdef OPENSSL
  ssl_decoder *d;

  if(!dp || !*dp)
    return 0;
  d = *dp;
  r_data_destroy(&d->client_random);
  r_data_destroy(&d->server_random);
  r_data_destroy(&d->session_id);
  r_data_destroy(&d->PMS);
  r_data_destroy(&d->MS);
  r_data_destroy(&d->handshake_messages);
  r_data_destroy(&d->session_hash);
  ssl_destroy_rec_decoder(&d->c_to_s);
  ssl_destroy_rec_decoder(&d->c_to_s_n);
  ssl_destroy_rec_decoder(&d->s_to_c);
  ssl_destroy_rec_decoder(&d->s_to_c_n);
  free(d);
  *dp = 0;
#endif
  return 0;
}

int ssl_set_client_random(ssl_decoder *d, UCHAR *msg, int len) {
#ifdef OPENSSL
  int r;

  r_data_destroy(&d->client_random);
  if((r = r_data_create(&d->client_random, msg, len)))
    ERETURN(r);
#endif
  return 0;
}

int ssl_set_server_random(ssl_decoder *d, UCHAR *msg, int len) {
#ifdef OPENSSL
  int r;

  r_data_destroy(&d->server_random);
  if((r = r_data_create(&d->server_random, msg, len)))
    ERETURN(r);
#endif
  return 0;
}

int ssl_set_client_session_id(ssl_decoder *d, UCHAR *msg, int len) {
#ifdef OPENSSL
  int r;

  if(len > 0) {
    r_data_destroy(&d->session_id);
    if((r = r_data_create(&d->session_id, msg, len)))
      ERETURN(r);
  }
#endif
  return 0;
}

int ssl_process_server_session_id(ssl_obj *ssl,
                                  ssl_decoder *d,
                                  UCHAR *msg,
                                  int len) {
#ifdef OPENSSL
  int r, _status;
  Data idd;
  int restored = 0;

  INIT_DATA(idd, msg, len);

  if(ssl->version == TLSV13_VERSION) {
    // No need to save/restore session in tls1.3 since the only way of
    // decrypting is through log file
  } else {
    /* First check to see if the client tried to restore */
    if(d->session_id) {
      /* Now check to see if we restored */
      if((r = r_data_compare(&idd, d->session_id)))
        ABORT(r);

      /* Now try to look up the session. We may not be able
         to find it if, for instance, the original session
         was initiated with something other than static RSA */
      if((r = ssl_restore_session(ssl, d)))
        ABORT(r);

      restored = 1;
    }
  }

  _status = 0;
abort:
  if(!restored) {
    /* Copy over the session ID */
    r_data_destroy(&d->session_id);
    r_data_create(&d->session_id, msg, len);
  }
  return _status;
#else
  return 0;
#endif
}

int ssl_process_client_session_id(ssl_obj *ssl,
                                  ssl_decoder *d,
                                  UCHAR *msg,
                                  int len) {
#ifdef OPENSSL
  int _status;

  /* First check if the client set session id */
  // todo: check that session_id in decoder and msg are the same (and if not
  // then take from msg?)
  if(d->session_id) {
    /* Remove the master secret */
    // todo: better save and destroy only when successfully read key log
    r_data_destroy(&d->MS);

    if(d->ctx->ssl_key_log_file && (ssl_read_key_log_file(ssl, d) == 0) &&
       d->MS) {
      // we found master secret for session in keylog
      // try to save session
      _status = ssl_save_session(ssl, d);
    } else {
      // just return error
      _status = -1;
    }
  } else {
    _status = -1;
  }
  return _status;
#else
  return 0;
#endif
}

int ssl_process_handshake_finished(ssl_obj *ssl, ssl_decoder *dec, Data *data) {
  if(ssl->version == TLSV13_VERSION) {
    if(ssl->direction ==
       DIR_I2R) {  // Change from handshake decoder to data traffic decoder
      dec->c_to_s = dec->c_to_s_n;
      dec->c_to_s_n = 0;
    } else {
      dec->s_to_c = dec->s_to_c_n;
      dec->s_to_c_n = 0;
    }
  }
  return 0;
}

int ssl_process_change_cipher_spec(ssl_obj *ssl,
                                   ssl_decoder *d,
                                   int direction) {
#ifdef OPENSSL
  if(ssl->version != TLSV13_VERSION) {
    if(direction == DIR_I2R) {
      d->c_to_s = d->c_to_s_n;
      d->c_to_s_n = 0;
      if(d->c_to_s)
        ssl->process_ciphertext |= direction;
    } else {
      d->s_to_c = d->s_to_c_n;
      d->s_to_c_n = 0;
      if(d->s_to_c)
        ssl->process_ciphertext |= direction;
    }
  }
#endif
  return 0;
}
int ssl_decode_record(ssl_obj *ssl,
                      ssl_decoder *dec,
                      int direction,
                      int ct,
                      int version,
                      Data *d) {
  ssl_rec_decoder *rd;
  UCHAR *out;
  int outl;
  int r, _status;
  UINT4 state;

  if(dec)
    rd = (direction == DIR_I2R) ? dec->c_to_s : dec->s_to_c;
  else
    rd = 0;
  state = (direction == DIR_I2R) ? ssl->i_state : ssl->r_state;

  if(ssl->version == TLSV13_VERSION &&
     ct != 23) {  // Only type 23 is encrypted in tls1.3
    ssl->record_encryption = REC_PLAINTEXT;
    return 0;
  } else if(!rd) {
    if(state & SSL_ST_SENT_CHANGE_CIPHER_SPEC) {
      ssl->record_encryption = REC_CIPHERTEXT;
      return SSL_NO_DECRYPT;
    } else {
      ssl->record_encryption = REC_PLAINTEXT;
      return 0;
    }
  }

  ssl->record_encryption = REC_CIPHERTEXT;
#ifdef OPENSSL
  if(!(out = (UCHAR *)malloc(d->len)))
    ABORT(R_NO_MEMORY);

  if(ssl->version == TLSV13_VERSION) {
    r = tls13_decode_rec_data(ssl, rd, ct, version, d->data, d->len, out,
                              &outl);
  } else {
    r = ssl_decode_rec_data(ssl, rd, ct, version, d->data, d->len, out, &outl);
  }
  if(r) {
    ABORT(r);
  }

  memcpy(d->data, out, outl);
  d->len = outl;

  ssl->record_encryption = REC_DECRYPTED_CIPHERTEXT;

  _status = 0;
abort:
  FREE(out);
  return _status;
#else
  return 0;
#endif
}

int ssl_update_handshake_messages(ssl_obj *ssl, Data *data) {
#ifdef OPENSSL
  Data *hms;
  UCHAR *d;
  int l, r;

  hms = ssl->decoder->handshake_messages;
  d = data->data - 4;
  l = data->len + 4;

  if(hms) {
    if(!(hms->data = realloc(hms->data, l + hms->len)))
      ERETURN(R_NO_MEMORY);

    memcpy(hms->data + hms->len, d, l);
    hms->len += l;
  } else {
    if((r = r_data_create(&hms, d, l)))
      ERETURN(r);
    ssl->decoder->handshake_messages = hms;
  }
#endif
  return 0;
}

static int ssl_create_session_lookup_key(ssl_obj *ssl,
                                         UCHAR *id,
                                         UINT4 idlen,
                                         UCHAR **keyp,
                                         UINT4 *keyl) {
  UCHAR *key = 0;
  UINT4 l;
  int _status;

  l = idlen + strlen(ssl->server_name) + idlen + 15; /* HOST + PORT + id */

  if(!(key = (UCHAR *)malloc(l)))
    ABORT(R_NO_MEMORY);
  *keyp = key;

  memcpy(key, id, idlen);
  *keyl = idlen;
  key += idlen;

  snprintf((char *)key, l, "%s:%d", ssl->server_name, ssl->server_port);
  *keyl += strlen((char *)key);

  _status = 0;
abort:
  return _status;
}

/* Look up the session id in the session cache and generate
   the appropriate keying material */
int ssl_restore_session(ssl_obj *ssl, ssl_decoder *d) {
  UCHAR *lookup_key = 0;
  void *msv;
  Data *msd;
  int lookup_key_len;
  int r, _status;
#ifdef OPENSSL
  if((r = ssl_create_session_lookup_key(ssl, d->session_id->data,
                                        d->session_id->len, &lookup_key,
                                        (UINT4 *)&lookup_key_len)))
    ABORT(r);
  if((r = r_assoc_fetch(d->ctx->session_cache, (char *)lookup_key,
                        lookup_key_len, &msv)))
    ABORT(r);
  msd = (Data *)msv;
  if((r = r_data_create(&d->MS, msd->data, msd->len)))
    ABORT(r);
  CRDUMPD("Restored MS", d->MS);

  switch(ssl->version) {
    case SSLV3_VERSION:
    case TLSV1_VERSION:
    case TLSV11_VERSION:
    case TLSV12_VERSION:
    case TLSV13_VERSION:
      if((r = ssl_generate_keying_material(ssl, d)))
        ABORT(r);
      break;
    default:
      ABORT(SSL_CANT_DO_CIPHER);
  }

  _status = 0;
abort:
  FREE(lookup_key);
  return _status;
#else
  return 0;
#endif
}

/* Look up the session id in the session cache and generate
   the appropriate keying material */
int ssl_save_session(ssl_obj *ssl, ssl_decoder *d) {
#ifdef OPENSSL
  UCHAR *lookup_key = 0;
  Data *msd = 0;
  int lookup_key_len;
  int r, _status;

  if((r = ssl_create_session_lookup_key(ssl, d->session_id->data,
                                        d->session_id->len, &lookup_key,
                                        (UINT4 *)&lookup_key_len)))
    ABORT(r);
  if((r = r_data_create(&msd, d->MS->data, d->MS->len)))
    ABORT(r);
  if((r = r_assoc_insert(d->ctx->session_cache, (char *)lookup_key,
                         lookup_key_len, (void *)msd, 0,
                         (int (*)(void *))r_data_zfree,
                         R_ASSOC_NEW | R_ASSOC_REPLACE)))
    ABORT(r);

  _status = 0;
abort:
  if(_status) {
    r_data_zfree(msd);
  }
  FREE(lookup_key);
  return _status;
#else
  return 0;
#endif
}

/* This only works with RSA because the other cipher suites
   offer PFS. Yuck. */
int ssl_process_client_key_exchange(ssl_obj *ssl,
                                    ssl_decoder *d,
                                    UCHAR *msg,
                                    int len) {
#ifdef OPENSSL
  int r, _status;
  int i;
  EVP_PKEY *pk;
  const BIGNUM *n;

  /* Remove the master secret if it was there
     to force keying material regeneration in
     case we're renegotiating */
  r_data_destroy(&d->MS);

  if(!d->ctx->ssl_key_log_file || ssl_read_key_log_file(ssl, d) || !d->MS) {
    if(ssl->cs->kex != KEX_RSA)
      return -1;

    if(d->ephemeral_rsa)
      return -1;

    pk = SSL_get_privatekey(d->ctx->ssl);
    if(!pk)
      return -1;

    if(EVP_PKEY_id(pk) != EVP_PKEY_RSA)
      return -1;

    RSA_get0_key(EVP_PKEY_get0_RSA(pk), &n, NULL, NULL);
    if((r = r_data_alloc(&d->PMS, BN_num_bytes(n))))
      ABORT(r);

    i = RSA_private_decrypt(len, msg, d->PMS->data, EVP_PKEY_get0_RSA(pk),
                            RSA_PKCS1_PADDING);

    if(i != 48)
      ABORT(SSL_BAD_PMS);

    d->PMS->len = 48;

    CRDUMPD("PMS", d->PMS);
  }

  switch(ssl->version) {
    case SSLV3_VERSION:
    case TLSV1_VERSION:
    case TLSV11_VERSION:
    case TLSV12_VERSION:
    case TLSV13_VERSION:
      if((r = ssl_generate_keying_material(ssl, d)))
        ABORT(r);
      break;
    default:
      ABORT(SSL_CANT_DO_CIPHER);
  }

  /* Now store the data in the session cache */
  if((r = ssl_save_session(ssl, d)))
    ABORT(r);

  _status = 0;
abort:
  return _status;
#else
  return 0;
#endif
}

#ifdef OPENSSL
static int tls_P_hash(ssl_obj *ssl,
                      Data *secret,
                      Data *seed,
                      const EVP_MD *md,
                      Data *out) {
  UCHAR *ptr = out->data;
  int left = out->len;
  int tocpy;
  UCHAR *A;
  UCHAR _A[128], tmp[128];
  unsigned int A_l, tmp_l;
  HMAC_CTX *hm = HMAC_CTX_new();

  CRDUMPD("P_hash secret", secret);
  CRDUMPD("P_hash seed", seed);

  A = seed->data;
  A_l = seed->len;

  while(left) {
    HMAC_Init_ex(hm, secret->data, secret->len, md, NULL);
    HMAC_Update(hm, A, A_l);
    HMAC_Final(hm, _A, &A_l);
    A = _A;

    HMAC_Init_ex(hm, secret->data, secret->len, md, NULL);
    HMAC_Update(hm, A, A_l);
    HMAC_Update(hm, seed->data, seed->len);
    HMAC_Final(hm, tmp, &tmp_l);

    tocpy = MIN(left, tmp_l);
    memcpy(ptr, tmp, tocpy);
    ptr += tocpy;
    left -= tocpy;
  }

  HMAC_CTX_free(hm);
  CRDUMPD("P_hash out", out);

  return 0;
}

static int tls_prf(ssl_obj *ssl,
                   Data *secret,
                   char *usage,
                   Data *rnd1,
                   Data *rnd2,
                   Data *out) {
  int r, _status;
  Data *md5_out = 0, *sha_out = 0;
  Data *seed;
  UCHAR *ptr;
  Data *S1 = 0, *S2 = 0;
  int i, S_l;

  if((r = r_data_alloc(&md5_out, MAX(out->len, 16))))
    ABORT(r);
  if((r = r_data_alloc(&sha_out, MAX(out->len, 20))))
    ABORT(r);
  if((r = r_data_alloc(&seed, strlen(usage) + rnd1->len + rnd2->len)))
    ABORT(r);
  ptr = seed->data;
  memcpy(ptr, usage, strlen(usage));
  ptr += strlen(usage);
  memcpy(ptr, rnd1->data, rnd1->len);
  ptr += rnd1->len;
  memcpy(ptr, rnd2->data, rnd2->len);
  ptr += rnd2->len;

  S_l = secret->len / 2 + secret->len % 2;

  if((r = r_data_alloc(&S1, S_l)))
    ABORT(r);
  if((r = r_data_alloc(&S2, S_l)))
    ABORT(r);

  memcpy(S1->data, secret->data, S_l);
  memcpy(S2->data, secret->data + (secret->len - S_l), S_l);

  if((r = tls_P_hash(ssl, S1, seed, EVP_get_digestbyname("MD5"), md5_out)))
    ABORT(r);
  if((r = tls_P_hash(ssl, S2, seed, EVP_get_digestbyname("SHA1"), sha_out)))
    ABORT(r);

  for(i = 0; i < out->len; i++)
    out->data[i] = md5_out->data[i] ^ sha_out->data[i];

  CRDUMPD("PRF out", out);
  _status = 0;
abort:
  r_data_destroy(&md5_out);
  r_data_destroy(&sha_out);
  r_data_destroy(&seed);
  r_data_destroy(&S1);
  r_data_destroy(&S2);
  return _status;
}

static int tls12_prf(ssl_obj *ssl,
                     Data *secret,
                     char *usage,
                     Data *rnd1,
                     Data *rnd2,
                     Data *out)

{
  const EVP_MD *md;
  int r, _status;
  Data *sha_out = 0;
  Data *seed;
  UCHAR *ptr;
  int i, dgi;

  if((r = r_data_alloc(&sha_out, MAX(out->len, 64)))) /* assume max SHA512 */
    ABORT(r);
  if((r = r_data_alloc(&seed, strlen(usage) + rnd1->len + rnd2->len)))
    ABORT(r);
  ptr = seed->data;
  memcpy(ptr, usage, strlen(usage));
  ptr += strlen(usage);
  memcpy(ptr, rnd1->data, rnd1->len);
  ptr += rnd1->len;
  memcpy(ptr, rnd2->data, rnd2->len);
  ptr += rnd2->len;

  /* Earlier versions of openssl didn't have SHA256 of course... */
  dgi = MAX(DIG_SHA256, ssl->cs->dig);
  dgi -= 0x40;
  if((md = EVP_get_digestbyname(digests[dgi])) == NULL) {
    DBG((0, "Cannot get EVP for digest %s, openssl library current?",
         digests[dgi]));
    ERETURN(SSL_BAD_MAC);
  }
  if((r = tls_P_hash(ssl, secret, seed, md, sha_out)))
    ABORT(r);

  for(i = 0; i < out->len; i++)
    out->data[i] = sha_out->data[i];

  CRDUMPD("PRF out", out);
  _status = 0;
abort:
  r_data_destroy(&sha_out);
  r_data_destroy(&seed);
  return _status;
}

static int ssl3_generate_export_iv(ssl_obj *ssl,
                                   Data *r1,
                                   Data *r2,
                                   Data *out) {
  MD5_CTX md5;
  UCHAR tmp[16];

  MD5_Init(&md5);
  MD5_Update(&md5, r1->data, r1->len);
  MD5_Update(&md5, r2->data, r2->len);
  MD5_Final(tmp, &md5);

  memcpy(out->data, tmp, out->len);

  return 0;
}

static int ssl3_prf(ssl_obj *ssl,
                    Data *secret,
                    char *usage,
                    Data *r1,
                    Data *r2,
                    Data *out) {
  MD5_CTX md5;
  SHA_CTX sha;
  Data *rnd1, *rnd2;
  int off;
  int i = 0, j;
  UCHAR buf[20];

  rnd1 = r1;
  rnd2 = r2;

  CRDUMPD("Secret", secret);
  CRDUMPD("RND1", rnd1);
  CRDUMPD("RND2", rnd2);

  MD5_Init(&md5);
  memset(&sha, 0, sizeof(sha));
  SHA1_Init(&sha);

  for(off = 0; off < out->len; off += 16) {
    char outbuf[16];
    int tocpy;
    i++;

    /* A, BB, CCC,  ... */
    for(j = 0; j < i; j++) {
      buf[j] = 64 + i;
    }

    SHA1_Update(&sha, buf, i);
    CRDUMP("BUF", buf, i);
    if(secret)
      SHA1_Update(&sha, secret->data, secret->len);
    CRDUMPD("secret", secret);

    if(!strcmp(usage, "client write key") ||
       !strcmp(usage, "server write key")) {
      SHA1_Update(&sha, rnd2->data, rnd2->len);
      CRDUMPD("rnd2", rnd2);
      SHA1_Update(&sha, rnd1->data, rnd1->len);
      CRDUMPD("rnd1", rnd1);
    } else {
      SHA1_Update(&sha, rnd1->data, rnd1->len);
      CRDUMPD("rnd1", rnd1);
      SHA1_Update(&sha, rnd2->data, rnd2->len);
      CRDUMPD("rnd2", rnd2);
    }

    SHA1_Final(buf, &sha);
    CRDUMP("SHA out", buf, 20);

    SHA1_Init(&sha);

    MD5_Update(&md5, secret->data, secret->len);
    MD5_Update(&md5, buf, 20);
    MD5_Final((unsigned char *)outbuf, &md5);
    tocpy = MIN(out->len - off, 16);
    memcpy(out->data + off, outbuf, tocpy);
    CRDUMP("MD5 out", (UCHAR *)outbuf, 16);

    MD5_Init(&md5);
  }

  return 0;
}

static int ssl_generate_keying_material(ssl_obj *ssl, ssl_decoder *d) {
  Data *key_block = 0, temp;
  UCHAR _iv_c[8], _iv_s[8];
  UCHAR _key_c[16], _key_s[16];
  int needed;
  int r, _status;
  UCHAR *ptr, *c_wk, *s_wk, *c_mk = NULL, *s_mk = NULL, *c_iv = NULL,
                            *s_iv = NULL;

  if(!d->MS) {
    if((r = r_data_alloc(&d->MS, 48)))
      ABORT(r);

    if(ssl->extensions->extended_master_secret == 2) {
      if((r = ssl_generate_session_hash(ssl, d)))
        ABORT(r);

      temp.len = 0;
      if((r = PRF(ssl, d->PMS, "extended master secret", d->session_hash, &temp,
                  d->MS)))
        ABORT(r);
    } else if((r = PRF(ssl, d->PMS, "master secret", d->client_random,
                       d->server_random, d->MS)))
      ABORT(r);

    CRDUMPD("MS", d->MS);
  }

  /* Compute the key block. First figure out how much data
       we need*/
  /* Ideally find a cleaner way to check for AEAD cipher */
  needed = !IS_AEAD_CIPHER(ssl->cs) ? ssl->cs->dig_len * 2 : 0;
  needed += ssl->cs->bits / 4;
  if(ssl->cs->block > 1)
    needed += ssl->cs->block * 2;

  if((r = r_data_alloc(&key_block, needed)))
    ABORT(r);
  if((r = PRF(ssl, d->MS, "key expansion", d->server_random, d->client_random,
              key_block)))
    ABORT(r);

  ptr = key_block->data;
  /* Ideally find a cleaner way to check for AEAD cipher */
  if(!IS_AEAD_CIPHER(ssl->cs)) {
    c_mk = ptr;
    ptr += ssl->cs->dig_len;
    s_mk = ptr;
    ptr += ssl->cs->dig_len;
  }

  c_wk = ptr;
  ptr += ssl->cs->eff_bits / 8;
  s_wk = ptr;
  ptr += ssl->cs->eff_bits / 8;

  if(ssl->cs->block > 1) {
    c_iv = ptr;
    ptr += ssl->cs->block;
    s_iv = ptr;
    ptr += ssl->cs->block;
  }

  if(ssl->cs->export) {
    Data iv_c, iv_s;
    Data key_c, key_s;
    Data k;

    if(ssl->cs->block > 1) {
      ATTACH_DATA(iv_c, _iv_c);
      ATTACH_DATA(iv_s, _iv_s);

      if(ssl->version == SSLV3_VERSION) {
        if((r = ssl3_generate_export_iv(ssl, d->client_random, d->server_random,
                                        &iv_c)))
          ABORT(r);
        if((r = ssl3_generate_export_iv(ssl, d->server_random, d->client_random,
                                        &iv_s)))
          ABORT(r);
      } else {
        UCHAR _iv_block[16];
        Data iv_block;
        Data key_null;
        UCHAR _key_null;

        INIT_DATA(key_null, &_key_null, 0);

        /* We only have room for 8 bit IVs, but that's
           all we should need. This is a sanity check */
        if(ssl->cs->block > 8)
          ABORT(R_INTERNAL);

        ATTACH_DATA(iv_block, _iv_block);

        if((r = PRF(ssl, &key_null, "IV block", d->client_random,
                    d->server_random, &iv_block)))
          ABORT(r);

        memcpy(_iv_c, iv_block.data, 8);
        memcpy(_iv_s, iv_block.data + 8, 8);
      }

      c_iv = _iv_c;
      s_iv = _iv_s;
    }

    if(ssl->version == SSLV3_VERSION) {
      MD5_CTX md5;

      MD5_Init(&md5);
      MD5_Update(&md5, c_wk, ssl->cs->eff_bits / 8);
      MD5_Update(&md5, d->client_random->data, d->client_random->len);
      MD5_Update(&md5, d->server_random->data, d->server_random->len);
      MD5_Final(_key_c, &md5);
      c_wk = _key_c;

      MD5_Init(&md5);
      MD5_Update(&md5, s_wk, ssl->cs->eff_bits / 8);
      MD5_Update(&md5, d->server_random->data, d->server_random->len);
      MD5_Update(&md5, d->client_random->data, d->client_random->len);
      MD5_Final(_key_s, &md5);
      s_wk = _key_s;
    } else {
      ATTACH_DATA(key_c, _key_c);
      ATTACH_DATA(key_s, _key_s);
      INIT_DATA(k, c_wk, ssl->cs->eff_bits / 8);
      if((r = PRF(ssl, &k, "client write key", d->client_random,
                  d->server_random, &key_c)))
        ABORT(r);
      c_wk = _key_c;
      INIT_DATA(k, s_wk, ssl->cs->eff_bits / 8);
      if((r = PRF(ssl, &k, "server write key", d->client_random,
                  d->server_random, &key_s)))
        ABORT(r);
      s_wk = _key_s;
    }
  }

  if(!IS_AEAD_CIPHER(ssl->cs)) {
    CRDUMP("Client MAC key", c_mk, ssl->cs->dig_len);
    CRDUMP("Server MAC key", s_mk, ssl->cs->dig_len);
  }
  CRDUMP("Client Write key", c_wk, ssl->cs->bits / 8);
  CRDUMP("Server Write key", s_wk, ssl->cs->bits / 8);

  if(ssl->cs->block > 1) {
    CRDUMP("Client Write IV", c_iv, ssl->cs->block);
    CRDUMP("Server Write IV", s_iv, ssl->cs->block);
  }

  if((r = ssl_create_rec_decoder(&d->c_to_s_n, ssl, c_mk, c_wk, c_iv)))
    ABORT(r);
  if((r = ssl_create_rec_decoder(&d->s_to_c_n, ssl, s_mk, s_wk, s_iv)))
    ABORT(r);

  _status = 0;
abort:
  if(key_block) {
    r_data_zfree(key_block);
    free(key_block);
  }
  return _status;
}

static int hkdf_expand_label(ssl_obj *ssl,
                             ssl_decoder *d,
                             Data *secret,
                             char *label,
                             Data *context,
                             uint16_t length,
                             UCHAR **out) {
  int r = -1;
  size_t outlen = length;
  EVP_PKEY_CTX *pctx;

  pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL);

  Data hkdf_label;
  UCHAR *ptr;

  // Construct HkdfLabel
  hkdf_label.data = ptr = malloc(512);
  *(uint16_t *)ptr = ntohs(length);
  ptr += 2;
  *(uint8_t *)ptr++ = 6 + (label ? strlen(label) : 0);
  memcpy(ptr, "tls13 ", 6);
  ptr += 6;
  if(label) {
    memcpy(ptr, label, strlen(label));
    ptr += strlen(label);
  }
  *(uint8_t *)ptr++ = context ? context->len : 0;
  if(context) {
    memcpy(ptr, context->data, context->len);
    ptr += context->len;
  }
  hkdf_label.len = ptr - hkdf_label.data;
  CRDUMPD("hkdf_label", &hkdf_label);
  // Load parameters
  *out = malloc(length);
  if(EVP_PKEY_derive_init(pctx) <= 0) {
    fprintf(stderr, "EVP_PKEY_derive_init failed\n");
  }
  /* Error */
  if(EVP_PKEY_CTX_hkdf_mode(pctx, EVP_PKEY_HKDEF_MODE_EXPAND_ONLY) <= 0) {
    fprintf(stderr, "EVP_PKEY_CTX_hkdf_mode failed\n");
    goto abort;
  }
  if(EVP_PKEY_CTX_set_hkdf_md(
         pctx, EVP_get_digestbyname(digests[ssl->cs->dig - 0x40])) <= 0) {
    fprintf(stderr, "EVP_PKEY_CTX_set_hkdf_md failed\n");
    goto abort;
  }
  if(EVP_PKEY_CTX_set1_hkdf_key(pctx, secret->data, secret->len) <= 0) {
    fprintf(stderr, "EVP_PKEY_CTX_set_hkdf_md failed\n");
    goto abort;
  }
  if(EVP_PKEY_CTX_add1_hkdf_info(pctx, hkdf_label.data, hkdf_label.len) <= 0) {
    fprintf(stderr, "EVP_PKEY_CTX_add1_hkdf_info failed\n");
    goto abort;
  }
  if(EVP_PKEY_derive(pctx, *out, &outlen) <= 0) {
    fprintf(stderr, "EVP_PKEY_derive failed\n");
    goto abort;
  }

  CRDUMP("out_hkdf", *out, outlen);
  return 0;
abort:
  ERR_print_errors_fp(stderr);
  return r;
}

// Will update the keys for the particular direction
int ssl_tls13_update_keying_material(ssl_obj *ssl,
                                     ssl_decoder *d,
                                     int direction) {
  Data *secret;
  ssl_rec_decoder *decoder;
  UCHAR *newsecret;
  UCHAR *newkey;
  UCHAR *newiv;

  if(direction == DIR_I2R) {
    secret = d->CTS;
    decoder = d->c_to_s;
  } else {
    secret = d->STS;
    decoder = d->s_to_c;
  }
  hkdf_expand_label(ssl, d, secret, "traffic upd", NULL, ssl->cs->dig_len,
                    &newsecret);
  secret->data = newsecret;
  hkdf_expand_label(ssl, d, secret, "key", NULL, ssl->cs->eff_bits / 8,
                    &newkey);
  hkdf_expand_label(ssl, d, secret, "iv", NULL, 12, &newiv);
  tls13_update_rec_key(decoder, newkey, newiv);

  return 0;
}

int ssl_tls13_generate_keying_material(ssl_obj *ssl, ssl_decoder *d) {
  int r, _status;
  UCHAR *s_wk_h, *s_iv_h, *c_wk_h, *c_iv_h, *s_wk, *s_iv, *c_wk, *c_iv;
  if(!(d->ctx->ssl_key_log_file && ssl_read_key_log_file(ssl, d) == 0 &&
       d->SHTS && d->CHTS && d->STS && d->CTS)) {
    ABORT(-1);
  }
  // It is 12 for all ciphers
  if(hkdf_expand_label(ssl, d, d->SHTS, "key", NULL, ssl->cs->eff_bits / 8,
                       &s_wk_h)) {
    fprintf(stderr, "s_wk_h hkdf_expand_label failed\n");
    ABORT(-1);
  }
  if(hkdf_expand_label(ssl, d, d->SHTS, "iv", NULL, 12, &s_iv_h)) {
    fprintf(stderr, "s_iv_h hkdf_expand_label failed\n");
    ABORT(-1);
  }
  if(hkdf_expand_label(ssl, d, d->CHTS, "key", NULL, ssl->cs->eff_bits / 8,
                       &c_wk_h)) {
    fprintf(stderr, "c_wk_h hkdf_expand_label failed\n");
    ABORT(-1);
  }
  if(hkdf_expand_label(ssl, d, d->CHTS, "iv", NULL, 12, &c_iv_h)) {
    fprintf(stderr, "c_iv_h hkdf_expand_label failed\n");
    ABORT(-1);
  }
  if(hkdf_expand_label(ssl, d, d->STS, "key", NULL, ssl->cs->eff_bits / 8,
                       &s_wk)) {
    fprintf(stderr, "s_wk hkdf_expand_label failed\n");
    ABORT(-1);
  }
  if(hkdf_expand_label(ssl, d, d->STS, "iv", NULL, 12, &s_iv)) {
    fprintf(stderr, "s_iv hkdf_expand_label failed\n");
    ABORT(-1);
  }
  if(hkdf_expand_label(ssl, d, d->CTS, "key", NULL, ssl->cs->eff_bits / 8,
                       &c_wk)) {
    fprintf(stderr, "c_wk hkdf_expand_label failed\n");
    ABORT(-1);
  }
  if(hkdf_expand_label(ssl, d, d->CTS, "iv", NULL, 12, &c_iv)) {
    fprintf(stderr, "c_iv hkdf_expand_label failed\n");
    ABORT(-1);
  }
  CRDUMP("Server Handshake Write key", s_wk_h, ssl->cs->eff_bits / 8);
  CRDUMP("Server Handshake IV", s_iv_h, 12);
  CRDUMP("Client Handshake Write key", c_wk_h, ssl->cs->eff_bits / 8);
  CRDUMP("Client Handshake IV", c_iv_h, 12);
  CRDUMP("Server Write key", s_wk, ssl->cs->eff_bits / 8);
  CRDUMP("Server IV", s_iv, 12);
  CRDUMP("Client Write key", c_wk, ssl->cs->eff_bits / 8);
  CRDUMP("Client IV", c_iv, 12);

  if((r = ssl_create_rec_decoder(&d->c_to_s_n, ssl, NULL, c_wk, c_iv)))
    ABORT(r);
  if((r = ssl_create_rec_decoder(&d->s_to_c_n, ssl, NULL, s_wk, s_iv)))
    ABORT(r);
  if((r = ssl_create_rec_decoder(&d->c_to_s, ssl, NULL, c_wk_h, c_iv_h)))
    ABORT(r);
  if((r = ssl_create_rec_decoder(&d->s_to_c, ssl, NULL, s_wk_h, s_iv_h)))
    ABORT(r);
  return 0;
abort:
  return _status;
}

static int ssl_generate_session_hash(ssl_obj *ssl, ssl_decoder *d) {
  int r, _status, dgi;
  unsigned int len;
  const EVP_MD *md;
  EVP_MD_CTX *dgictx = EVP_MD_CTX_create();

  if((r = r_data_alloc(&d->session_hash, EVP_MAX_MD_SIZE)))
    ABORT(r);

  switch(ssl->version) {
    case TLSV12_VERSION:
      dgi = MAX(DIG_SHA256, ssl->cs->dig) - 0x40;
      if((md = EVP_get_digestbyname(digests[dgi])) == NULL) {
        DBG((0, "Cannot get EVP for digest %s, openssl library current?",
             digests[dgi]));
        ERETURN(SSL_BAD_MAC);
      }

      EVP_DigestInit(dgictx, md);
      EVP_DigestUpdate(dgictx, d->handshake_messages->data,
                       d->handshake_messages->len);
      EVP_DigestFinal(dgictx, d->session_hash->data,
                      (unsigned int *)&d->session_hash->len);

      break;
    case SSLV3_VERSION:
    case TLSV1_VERSION:
    case TLSV11_VERSION:
      EVP_DigestInit(dgictx, EVP_get_digestbyname("MD5"));
      EVP_DigestUpdate(dgictx, d->handshake_messages->data,
                       d->handshake_messages->len);
      EVP_DigestFinal_ex(dgictx, d->session_hash->data,
                         (unsigned int *)&d->session_hash->len);

      EVP_DigestInit(dgictx, EVP_get_digestbyname("SHA1"));
      EVP_DigestUpdate(dgictx, d->handshake_messages->data,
                       d->handshake_messages->len);
      EVP_DigestFinal(dgictx, d->session_hash->data + d->session_hash->len,
                      &len);

      d->session_hash->len += len;
      break;
    default:
      ABORT(SSL_CANT_DO_CIPHER);
  }

  _status = 0;
abort:
  return _status;
}

static int read_hex_string(char *str, UCHAR *buf, int n) {
  unsigned int t;
  int i;
  for(i = 0; i < n; i++) {
    if(sscanf(str + i * 2, "%02x", &t) != 1)
      return -1;
    buf[i] = (char)t;
  }
  return 0;
}
static int ssl_read_key_log_file(ssl_obj *ssl, ssl_decoder *d) {
    int r = -1;
    int _status = -1, n, i;
    size_t l = 0;
    char *line = NULL, *d_client_random = NULL, *label = NULL, *client_random = NULL, *secret = NULL;

    if (!ssl || !d || !d->client_random || !d->client_random->data || !d->ctx || !d->ctx->ssl_key_log_file) {
        return -1;
    }

    if (ssl->version == TLSV13_VERSION && !ssl->cs) {
        return -1;
    }

    d_client_random = malloc((d->client_random->len * 2) + 1);
    if (!d_client_random) {
        return -1;
    }

    for (i = 0; i < d->client_random->len; i++) {
        if (snprintf(d_client_random + (i * 2), 3, "%02x", d->client_random->data[i]) != 2) {
            ABORT(r);
        }
    }

    while ((n = getline(&line, &l, d->ctx->ssl_key_log_file)) != -1) {
        if (line[n - 1] == '\n') {
            line[n - 1] = '\0';
        }

        label = strtok(line, " ");
        client_random = strtok(NULL, " ");
        secret = strtok(NULL, " ");

        if (!label || !client_random || strlen(client_random) != 64 || 
            !secret || (ssl->version == TLSV13_VERSION ? strlen(secret) != ssl->cs->dig_len * 2 : strlen(secret) != 96) ||
            strncmp(client_random, d_client_random, 64) != 0) {
            continue;
        }

        if (!strncmp(label, "CLIENT_RANDOM", 13)) {
            if ((r = r_data_alloc(&d->MS, 48)) || read_hex_string(secret, d->MS->data, 48)) {
                ABORT(r);
            }
        } else if (ssl->version == TLSV13_VERSION) {
            if (!strncmp(label, "SERVER_HANDSHAKE_TRAFFIC_SECRET", 31)) {
                if ((r = r_data_alloc(&d->SHTS, ssl->cs->dig_len)) || 
                    read_hex_string(secret, d->SHTS->data, ssl->cs->dig_len)) {
                    ABORT(r);
                }
            } else if (!strncmp(label, "CLIENT_HANDSHAKE_TRAFFIC_SECRET", 31)) {
                if ((r = r_data_alloc(&d->CHTS, ssl->cs->dig_len)) || 
                    read_hex_string(secret, d->CHTS->data, ssl->cs->dig_len)) {
                    ABORT(r);
                }
            } else if (!strncmp(label, "SERVER_TRAFFIC_SECRET_0", 23)) {
                if ((r = r_data_alloc(&d->STS, ssl->cs->dig_len)) || 
                    read_hex_string(secret, d->STS->data, ssl->cs->dig_len)) {
                    ABORT(r);
                }
            } else if (!strncmp(label, "CLIENT_TRAFFIC_SECRET_0", 23)) {
                if ((r = r_data_alloc(&d->CTS, ssl->cs->dig_len)) || 
                    read_hex_string(secret, d->CTS->data, ssl->cs->dig_len)) {
                    ABORT(r);
                }
            }
        }
      /*
       Eventually add support for other labels defined here:
       https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format
      */
    }

    _status = 0;

abort:
    if (d_client_random) {
        free(d_client_random);
    }
    if (line) {
        free(line);
    }
    if (d->ctx->ssl_key_log_file) {
        fseek(d->ctx->ssl_key_log_file, 0, SEEK_SET);
    }
    return _status;
}
#endif
