/*H********************************************************************************/
/*!
    \File protossl.c

    \Description
        This module is a from-scratch TLS implementation.  It does not use any
        third-party code of any kind and was developed entirely by EA.

    \Notes
        References:
            TLS1.0 RFC: https://tools.ietf.org/html/rfc2246
            TLS1.1 RFC: https://tools.ietf.org/html/rfc4346
            TLS1.2 RFC: https://tools.ietf.org/html/rfc5246
            TLS1.3 RFC: https://tools.ietf.org/html/rfc8446
            ASN.1 encoding rules: https://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf

    \Copyright
        Copyright (c) Electronic Arts 2002-2018

    \Version 03/08/2002 (gschaefer) Initial SSL 2.0 implementation
    \Version 03/03/2004 (sbevan)    Added certificate validation
    \Version 11/05/2005 (gschaefer) Rewritten to follow SSL 3.0 specification
    \Version 10/12/2012 (jbrookes)  Added support for TLS1.0 & TLS1.1
    \Version 10/20/2013 (jbrookes)  Added server handshake, client cert support
    \Version 11/06/2013 (jbrookes)  Added support for TLS1.2
    \Version 03/31/2017 (eesponda)  Added support for EC ciphers
    \Version 03/28/2018 (jbrookes)  Added support for TLS1.3
    \Version 08/15/2018 (jbrookes)  Removed SSLv3 & RC4 ciphers
*/
/********************************************************************************H*/

/*** Include files ****************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "DirtySDK/dirtysock.h"
#include "DirtySDK/dirtysock/dirtycert.h"
#include "DirtySDK/dirtysock/dirtyerr.h"
#include "DirtySDK/dirtysock/dirtymem.h"

#include "DirtySDK/crypt/cryptdef.h"
#include "DirtySDK/crypt/cryptaes.h"
#include "DirtySDK/crypt/cryptchacha.h"
#include "DirtySDK/crypt/cryptcurve.h"
#include "DirtySDK/crypt/cryptgcm.h"
#include "DirtySDK/crypt/crypthash.h"
#include "DirtySDK/crypt/crypthmac.h"
#include "DirtySDK/crypt/cryptmd5.h"
#include "DirtySDK/crypt/cryptrand.h"
#include "DirtySDK/crypt/cryptrsa.h"
#include "DirtySDK/crypt/cryptsha1.h"
#include "DirtySDK/util/base64.h"

#include "DirtySDK/proto/protossl.h"

#include "cryptrandpriv.h"

/*** Defines **********************************************************************/

#define DEBUG_RAW_DATA  (DIRTYCODE_LOGGING && 0)  // display raw debug data
#define DEBUG_ENC_PERF  (DIRTYCODE_LOGGING && 0)  // show verbose crypto performance
#define DEBUG_MSG_LIST  (DIRTYCODE_LOGGING && 0)  // list the message states
#define DEBUG_ALL_OBJS  (DIRTYCODE_LOGGING && 0)  // display all headers/objects while parsing
#define DEBUG_VAL_CERT  (DIRTYCODE_LOGGING && 0)  // display verbose certificate validation info
#define DEBUG_RES_SESS  (DIRTYCODE_LOGGING && 0)  // display verbose session resume info
#define DEBUG_MOD_PRNT  (DIRTYCODE_LOGGING && 0)  // display public key modulus when parsing certificate (useful when adding new CA)

#define SSL_VERBOSE_DEFAULT         (1)

#define SSL_CACERTFLAG_NONE         (0)
#define SSL_CACERTFLAG_GOSCA        (1)                     //!< identifies a GOS CA, for use on internal (*.ea.com) servers only
#define SSL_CACERTFLAG_CAPROVIDER   (2)                     //!< identifies a CA that can function as a CA provider (used for cert loading)

#define SSL_ERR_GOSCA_INVALIDUSE    (-100)                  //!< error indicating invalid use of a GOS CA to access a non ea.com domain
#define SSL_ERR_CERT_INVALIDDATE    (-101)                  //!< error indicating a CA certificate is expired
#define SSL_ERR_CAPROVIDER_INVALID  (-102)                  //!< attempt to use an invalid CA for CA provider use

#define SSL_MIN_PACKET      5                               // smallest packet (5 bytes framing)
#define SSL_CRYPTO_PAD      2048                            // maximum crypto overhead ref: http://tools.ietf.org/html/rfc5246#section-6.2.3
#define SSL_RAW_PACKET      16384                           // max raw data size ref: http://tools.ietf.org/html/rfc5246#section-6.2.1
#define SSL_RCVMAX_PACKET   ((SSL_RAW_PACKET+SSL_CRYPTO_PAD)*2) // max recv packet buffer; sized to allow a handshake packet up to twice the ssl frame size
#define SSL_SNDMAX_PACKET   (SSL_RAW_PACKET)                // max send packet buffer
#define SSL_SNDOVH_PACKET   (384)                           // reserve space for header/mac in send packet
#define SSL_SNDLIM_PACKET   (SSL_SNDMAX_PACKET-SSL_SNDOVH_PACKET) // max send user payload size

#define SSL_KEYBLOCK_LEN    (512)                           //!< length of keyblock that holds generated key material/tls1.3 secrets/keys/ivs
#define SSL_KEYMATERIAL_LEN (256)                           //!< length of key material we generate for TLS1.2 and prior

#define SSL_SIG_MAX         (512)                           //!< max signature size (4096 bit)

#define SSL3_SESSION_TICKET_MAX (1536)                      //!< max session ticket size
#define SSL3_SESSION_NONCE_MAX  (256)                       //!< max session ticket nonce size

#define SSL_SESSHIST_MAX    (32)                            // max number of previous sessions that will be tracked for possible later resumption
#define SSL_SESSVALIDTIME_MAX (2*60*60*1000)                // session validity cached for a max of two hours (tls1.2 and prior)
#define SSL_SESSID_SIZE     (32)

#define SSL_CERTVALID_MAX     (32)
#define SSL_CERTVALIDTIME_MAX (2*60*60*1000)                // certificate validity cached for a max of two hours

#define SSL_FINGERPRINT_SIZE  (CRYPTSHA1_HASHSIZE)

// min/default/max protocol versions supported
#define SSL3_VERSION_MIN        (PROTOSSL_VERSION_TLS1_0)
#define SSL3_VERSION            (PROTOSSL_VERSION_TLS1_2)
#define SSL3_VERSION_MAX        (PROTOSSL_VERSION_TLS1_3)

// internal names for TLS protocol versions
#define SSL3_SSLv3              (0x0300)                    // defined internally strictly to identify SSLv3 cipher suites that are still supported
#define SSL3_TLS1_0             (PROTOSSL_VERSION_TLS1_0)
#define SSL3_TLS1_1             (PROTOSSL_VERSION_TLS1_1)
#define SSL3_TLS1_2             (PROTOSSL_VERSION_TLS1_2)
#define SSL3_TLS1_3             (PROTOSSL_VERSION_TLS1_3)

// TLS record types
#define SSL3_REC_CIPHER         20  // cipher change record
#define SSL3_REC_ALERT          21  // alert record
#define SSL3_REC_HANDSHAKE      22  // handshake record
#define SSL3_REC_APPLICATION    23  // application data record

// TLS handshake header length
#define SSL3_MSG_HEADER_SIZE    4

// TLS handshake message types; see https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-7
#define SSL3_MSG_HELLO_REQUEST          0   // hello request
#define SSL3_MSG_CLIENT_HELLO           1   // client hello
#define SSL3_MSG_SERVER_HELLO           2   // server hello
#define SSL3_MSG_NEW_SESSION_TICKET     4   // session ticket
#define SSL3_MSG_END_OF_EARLY_DATA      5   // end of early data (tls1.3)
#define SSL3_MSG_ENCRYPTED_EXTENSIONS   8   // encrypted extensions (tls1.3)
#define SSL3_MSG_CERTIFICATE            11  // certificate
#define SSL3_MSG_SERVER_KEY             12  // server key data
#define SSL3_MSG_CERT_REQ               13  // certificate request
#define SSL3_MSG_SERVER_DONE            14  // server handshake done
#define SSL3_MSG_CERT_VERIFY            15  // certificate verify
#define SSL3_MSG_CLIENT_KEY             16  // client key data
#define SSL3_MSG_FINISHED               20  // handshake finished
#define SSL3_MSG_KEY_UPDATE             24  // key update (tls1.3)
#define SSL3_MSG_MESSAGE_HASH           254 // synthetic message for HRR

// MAC types (ident equals size)
#define SSL3_MAC_NULL           0   // none
#define SSL3_MAC_MD5            16  // md5
#define SSL3_MAC_SHA            20  // sha1
#define SSL3_MAC_SHA256         32  // sha2-256
#define SSL3_MAC_SHA384         48  // sha2-384
#define SSL3_MAC_SHA512         64  // sha2-512
#define SSL3_MAC_MAXSIZE        (CRYPTHASH_MAXDIGEST) // maximum MAC size

// PRF types
#define SSL3_PRF_NULL           0   // no cipher-suite defined prf
#define SSL3_PRF_SHA256         CRYPTHASH_SHA256
#define SSL3_PRF_SHA384         CRYPTHASH_SHA384

// key types
#define SSL3_KEY_NULL           0   // no record key exchange
#define SSL3_KEY_RSA            1   // use rsa
#define SSL3_KEY_ECDHE          2   // use ecdhe

// signing types
#define SSL3_SIGN_RSA           1
#define SSL3_SIGN_ECDSA         64

// key lengths
#define SSL3_KEYLEN_NULL        0
#define SSL3_KEYLEN_128         16  // 128-bit key length in bytes
#define SSL3_KEYLEN_256         32  // 256-bit key length in bytes

// cipher types
#define SSL3_ENC_NULL           0   // no record encryption
#define SSL3_ENC_AES            1   // use aes
#define SSL3_ENC_GCM            2   // use gcm
#define SSL3_ENC_CHACHA         3   // use chacha

// hash ids
#define SSL3_HASHID_NONE        (0)
#define SSL3_HASHID_MD5         (1)
#define SSL3_HASHID_SHA1        (2)
#define SSL3_HASHID_SHA224      (3)
#define SSL3_HASHID_SHA256      (4)
#define SSL3_HASHID_SHA384      (5)
#define SSL3_HASHID_SHA512      (6)
#define SSL3_HASHID_MAX         (SSL3_HASHID_SHA512)

// signature algorithm ids
#define SSL3_SIGALG_NONE        (0)
#define SSL3_SIGALG_RSA         (1)
#define SSL3_SIGALG_DSA         (2)
#define SSL3_SIGALG_ECDSA       (3)

// signature verification schemes
#define SSL3_SIGVERIFY_NONE         (0)
#define SSL3_SIGVERIFY_RSA_PKCS1    (1)
#define SSL3_SIGVERIFY_RSA_PSS      (2)

// curve types
enum
{
    SSL3_CURVE_NONE = 0xff,
    SSL3_CURVE_SECP256R1 = 0,
    SSL3_CURVE_X25519,
    SSL3_CURVE_SECP384R1,
    SSL3_CURVE_X448,
    SSL3_NUM_CURVES
};
// index of default ec
#define SSL3_CURVE_DEFAULT      (SSL3_CURVE_SECP256R1)
// index of max ec
#define SSL3_CURVE_MAX          (SSL3_CURVE_X448)

/* signature schemes (tls1.3, but overlaps with previous signature algorithms); see
   https://tools.ietf.org/html/rfc8446#section-4.2.3 */

// RSASSA-PKCS1-v1_5 algorithms
#define SSL3_SIGSCHEME_RSA_PKCS1_MD5            (0x0101) // deprecated
#define SSL3_SIGSCHEME_RSA_PKCS1_SHA1           (0x0201) // legacy
#define SSL3_SIGSCHEME_RSA_PKCS1_SHA256         (0x0401)
#define SSL3_SIGSCHEME_RSA_PKCS1_SHA384         (0x0501)
#define SSL3_SIGSCHEME_RSA_PKCS1_SHA512         (0x0601)
// ECDSA algorithms
#define SSL3_SIGSCHEME_ECDSA_SHA1               (0x0203) // legacy, required for TLS1.0/1.1 ECDSA cipher suites
#define SSL3_SIGSCHEME_ECDSA_SHA256             (0x0403)
#define SSL3_SIGSCHEME_ECDSA_SHA384             (0x0503)
#define SSL3_SIGSCHEME_ECDSA_SHA512             (0x0603)
// RSASSA-PSS algorithms with public key OID rsaEncryption
#define SSL3_SIGSCHEME_RSA_PSS_RSAE_SHA256      (0x0804)
#define SSL3_SIGSCHEME_RSA_PSS_RSAE_SHA384      (0x0805)
#define SSL3_SIGSCHEME_RSA_PSS_RSAE_SHA512      (0x0806)
// EdDSA algorithms
#define SSL3_SIGSCHEME_EDDSA_ED25519            (0x0807) // unsupported
#define SSL3_SIGSCHEME_EDDSA_ED448              (0x0808) // unsupported
// RSASSA-PSS algorithms with public key OID RSASSA-PSS
#define SSL3_SIGSCHEME_RSA_PSS_PSS_SHA256       (0x0809)
#define SSL3_SIGSCHEME_RSA_PSS_PSS_SHA384       (0x080a)
#define SSL3_SIGSCHEME_RSA_PSS_PSS_SHA512       (0x080b)

// curve types
#define SSL3_CURVETYPE_EXPLICIT_PRIME   (1)
#define SSL3_CURVETYPE_EXPLICIT_CHAR    (2)
#define SSL3_CURVETYPE_NAMED_CURVE      (3)

// TLS alert defines

// alert level
#define SSL3_ALERT_LEVEL_WARNING   1
#define SSL3_ALERT_LEVEL_FATAL     2

// alert identifiers
#define SSL3_ALERT_DESC_CLOSE_NOTIFY                0
#define SSL3_ALERT_DESC_UNEXPECTED_MESSAGE          10
#define SSL3_ALERT_DESC_BAD_RECORD_MAC              20
#define SSL3_ALERT_DESC_DECRYPTION_FAILED           21  // reserved
#define SSL3_ALERT_DESC_RECORD_OVERFLOW             22
#define SSL3_ALERT_DESC_DECOMPRESSION_FAILURE       30  // reserved
#define SSL3_ALERT_DESC_HANDSHAKE_FAILURE           40
#define SSL3_ALERT_DESC_NO_CERTIFICATE              41  // reserved
#define SSL3_ALERT_DESC_BAD_CERTFICIATE             42
#define SSL3_ALERT_DESC_UNSUPPORTED_CERTIFICATE     43
#define SSL3_ALERT_DESC_CERTIFICATE_REVOKED         44
#define SSL3_ALERT_DESC_CERTIFICATE_EXPIRED         45
#define SSL3_ALERT_DESC_CERTIFICATE_UNKNOWN         46
#define SSL3_ALERT_DESC_ILLEGAL_PARAMETER           47
// the following alert types are all TLS only
#define SSL3_ALERT_DESC_UNKNOWN_CA                  48
#define SSL3_ALERT_DESC_ACCESS_DENIED               49
#define SSL3_ALERT_DESC_DECODE_ERROR                50
#define SSL3_ALERT_DESC_DECRYPT_ERROR               51
#define SSL3_ALERT_DESC_EXPORT_RESTRICTION          60 // reserved
#define SSL3_ALERT_DESC_PROTOCOL_VERSION            70
#define SSL3_ALERT_DESC_INSUFFICIENT_SECURITY       71
#define SSL3_ALERT_DESC_INTERNAL_ERROR              80
#define SSL3_ALERT_DESC_INAPPROPRIATE_FALLBACK      86
#define SSL3_ALERT_DESC_USER_CANCELLED              90
#define SSL3_ALERT_DESC_NO_RENEGOTIATION            100 // reserved
#define SSL3_ALERT_DESC_MISSING_EXTENSION           109
#define SSL3_ALERT_DESC_UNSUPPORTED_EXTENSION       110
#define SSL3_ALERT_DESC_CERTIFICATE_UNOBTAINABLE    111
#define SSL3_ALERT_DESC_UNRECOGNIZED_NAME           112
#define SSL3_ALERT_DESC_BAD_CERTIFICATE_STATUS      113
#define SSL3_ALERT_DESC_BAD_CERTIFICATE_HASH        114
#define SSL3_ALERT_DESC_UNKNOWN_PSK_IDENTITY        115
#define SSL3_ALERT_DESC_CERTIFICATE_REQUIRED        116
#define SSL3_ALERT_DESC_NO_APPLICATION_PROTOCOL     120

// certificate setup
#define SSL_CERT_X509   1

// tls extension types
#define SSL_EXTN_SERVER_NAME            (0x0000)    //!< identifier for server name extension
#define SSL_EXTN_ELLIPTIC_CURVES        (0x000a)    //!< identifier for elliptic curves extension and supported curves
#define SSL_EXTN_SIGNATURE_ALGORITHMS   (0x000d)    //!< identifier for signature algorithms extension
#define SSL_EXTN_ALPN                   (0x0010)    //!< identifier for ALPN (Application Level Protocol Negotiation) extension
#define SSL_EXTN_PRE_SHARED_KEY         (0x0029)    //!< identifier for pre_shared_key extension
#define SSL_EXTN_EARLY_DATA             (0x002a)    //!< identifier for early_data extension (not supported)
#define SSL_EXTN_SUPPORTED_VERSIONS     (0x002b)    //!< identifier for supported_versions extension
#define SSL_EXTN_COOKIE                 (0x002c)    //!< identifier for cookie extension
#define SSL_EXTN_PSK_MODES              (0x002d)    //!< identifier for psk_key_exchange_modes extension
#define SSL_EXTN_CERT_AUTH              (0x002f)    //!< identifier for certificate_authorities extension (not supported)
#define SSL_EXTN_OID_FILTER             (0x0030)    //!< idetntifer for oid_filter extension (not supported)
#define SSL_EXTN_POST_HANDSHAKE_AUTH    (0x0031)    //!< identifier for post_handshake_auth extension (not supported)
#define SSL_EXTN_SIGALGS_CERT           (0x0032)    //!< identifier for signature_algorithms_cert extension (not supported)
#define SSL_EXTN_KEY_SHARE              (0x0033)    //!< identifier for key_share extension
#define SSL_EXTN_MAX                    (SSL_EXTN_KEY_SHARE)    //!< max defined extension; renegotiation_info is special case

#define SSL_EXTN_RENEGOTIATION_INFO     (0xff01)     //!< renegotiation_info extension (not supported)

#define SSL_EXTN_ALL                    (0xffff)    //!< special value used to indicate all extensions, not a bitfield

// ALPN defines
#define SSL_ALPN_MAX_PROTOCOLS          (4)         //!< max supported ALPN protocols

// tls states
enum
{
    // network states
    ST_IDLE = 0,
    ST_ADDR,
    ST_CONN,
    ST_WAIT_CONN,
    ST_WAIT_CA,                 // waiting for the CA to be fetched

    // ssl states
    ST3_SEND_HELLO = 20,
    ST3_RECV_HELLO,
    ST3_SEND_HELLO_RETRY,
    ST3_SEND_EXTN,
    ST3_SEND_CERT,
    ST3_SEND_CERT_REQ,
    ST3_SEND_KEY,
    ST3_SEND_DONE,
    ST3_SEND_VERIFY,
    ST3_SEND_CHANGE,
    ST3_SEND_FINISH,
    ST3_RECV_CHANGE,
    ST3_RECV_FINISH,
    // post-finish states
    ST3_SEND_HELLO_REQUEST,
    ST3_PROC_ASYNC,             // synthetic state for iterative async operations
    ST3_SECURE,
    ST_UNSECURE,

    // failure states
    ST_FAIL = 0x1000,
    ST_FAIL_DNS,
    ST_FAIL_CONN,
    ST_FAIL_CONN_SSL2,
    ST_FAIL_CONN_NOTSSL,
    ST_FAIL_CONN_MINVERS,
    ST_FAIL_CONN_MAXVERS,
    ST_FAIL_CONN_NOCIPHER,
    ST_FAIL_CONN_NOCURVE,
    ST_FAIL_CERT_NONE,
    ST_FAIL_CERT_INVALID,
    ST_FAIL_CERT_HOST,
    ST_FAIL_CERT_NOTRUST,
    ST_FAIL_CERT_BADDATE,
    ST_FAIL_SETUP,
    ST_FAIL_SECURE,
    ST_FAIL_CERT_REQUEST
};

// ASN.1 definitions
#define ASN_CLASS_UNIV      0x00
#define ASN_CLASS_APPL      0x40
#define ASN_CLASS_CONT      0x80
#define ASN_CLASS_PRIV      0xc0

#define ASN_PRIMITIVE       0x00
#define ASN_CONSTRUCT       0x20

#define ASN_TYPE_BOOLEAN    0x01
#define ASN_TYPE_INTEGER    0x02
#define ASN_TYPE_BITSTRING  0x03
#define ASN_TYPE_OCTSTRING  0x04
#define ASN_TYPE_NULL       0x05
#define ASN_TYPE_OBJECT     0x06
#define ASN_TYPE_UTF8STR    0x0c
#define ASN_TYPE_SEQN       0x10
#define ASN_TYPE_SET        0x11
#define ASN_TYPE_PRINTSTR   0x13
#define ASN_TYPE_T61        0x14
#define ASN_TYPE_IA5        0x16
#define ASN_TYPE_UTCTIME    0x17
#define ASN_TYPE_GENERALIZEDTIME 0x18
#define ASN_TYPE_UNICODESTR 0x1e

#define ASN_IMPLICIT_TAG   0x80
#define ASN_EXPLICIT_TAG   0xa0

enum {
    ASN_OBJ_NONE = 0,
    ASN_OBJ_COUNTRY,
    ASN_OBJ_STATE,
    ASN_OBJ_CITY,
    ASN_OBJ_ORGANIZATION,
    ASN_OBJ_UNIT,
    ASN_OBJ_COMMON,
    ASN_OBJ_SUBJECT_ALT,
    ASN_OBJ_BASIC_CONSTRAINTS,
    ASN_OBJ_MD5,
    ASN_OBJ_SHA1,
    ASN_OBJ_SHA256,
    ASN_OBJ_SHA384,
    ASN_OBJ_SHA512,
    ASN_OBJ_RSA_PKCS_KEY,
    ASN_OBJ_ECDSA_KEY,
    ASN_OBJ_RSA_PKCS_MD5,
    ASN_OBJ_RSA_PKCS_SHA1,
    ASN_OBJ_RSA_PKCS_SHA256,
    ASN_OBJ_RSA_PKCS_SHA384,
    ASN_OBJ_RSA_PKCS_SHA512,
    ASN_OBJ_RSASSA_PSS,
    ASN_OBJ_ECDSA_SHA256,
    ASN_OBJ_ECDSA_SHA384,
    ASN_OBJ_ECDSA_SHA512,
    ASN_OBJ_ECDSA_SECP256R1,
    ASN_OBJ_ECDSA_SECP384R1,
    ASN_OBJ_ECDSA_SECP521R1,
    ASN_OBJ_PKCS1_MGF1,
    ASN_OBJ_COUNT,
};

/*** Macros ***********************************************************************/

/*** Type Definitions *************************************************************/

//! ASN object table
typedef struct ASNObjectT
{
    int32_t iType;              //!< symbolic type
    int32_t iSize;              //!< size of object
    uint8_t strData[16];        //!< object identifier
} ASNObjectT;

//! generic binary certificate container plus some parsed certificate info
typedef struct CertificateDataT
{
    int32_t iCrvType;
    int32_t iKeyType;
    int32_t iSigType;
    int32_t iCertSize;
    uint8_t aCertData[1];
} CertificateDataT;

//! structure holding .pem certificate signature begin/end strings
typedef struct CertificateSignatureT
{
    const char *pCertBeg;
    const char *pCertEnd;
    uint32_t uCertType;
} CertificateSignatureT;

//! note: this layout differs somewhat from x.509, but I think it
//! makes more sense this way
typedef struct X509CertificateT
{
    ProtoSSLCertIdentT Issuer;      //!< certificate was issued by (matches subject in another cert)
    ProtoSSLCertIdentT Subject;     //!< certificate was issued for this site/authority

    char strGoodFrom[32];           //!< good from this date
    char strGoodTill[32];           //!< until this date
    uint64_t uGoodFrom;
    uint64_t uGoodTill;

    const uint8_t *pSubjectAlt;     //!< subject alternative name, if present
    int32_t iSubjectAltLen;         //!< subject alternative length

    int32_t iSerialSize;            //!< certificate serial number
    uint8_t SerialData[32];         //!< certificate serial data

    int32_t iSigType;               //!< digital signature type
    int32_t iSigHash;               //!< signature hash algorithm
    int32_t iSigSize;               //!< size of signature data
    int32_t iSigSalt;               //!< salt length when signature algorithm is rsassa-pss
    int32_t iMgfHash;               //!< message-generation function hash when signature algorithm is rsassa-pss
    uint8_t SigData[SSL_SIG_MAX];   //!< digital signature data

    int32_t iKeyType;               //!< public key algorithm type
    int32_t iCrvType;               //!< type of curve, if keytype==ecdsa
    int32_t iKeyModSize;            //!< size of public key modulus (rsa) / curve data (ecdsa)
    uint8_t KeyModData[SSL_SIG_MAX];//!< public key modulus
    int32_t iKeyExpSize;            //!< size of public key exponent
    uint8_t KeyExpData[16];         //!< public key exponent

    // iMaxHeight is valid only if iCertIsCA is set
    int32_t iCertIsCA;              //!< whether this cert can be used as a CA cert
    int32_t iMaxHeight;             //!< the pathLenConstraints of a CA, 0 means no limit/field not present

    int32_t iHashSize;              //!< size of certificate signature hash
    CryptHashTypeE eHashType;       //!< type of certificate signature hash
    uint8_t HashData[CRYPTHASH_MAXDIGEST];
} X509CertificateT;

//! private key definition
typedef struct X509PrivateKeyT
{
    CryptBinaryObjT Modulus;            //!< key modulus
    CryptBinaryObjT PublicExponent;     //!< public key exponent
    CryptBinaryObjT PrivateExponent;    //!< private key exponent
    CryptBinaryObjT PrimeP;             //!< prime factor (p) of modulus
    CryptBinaryObjT PrimeQ;             //!< prime factor (q) of modulus
    CryptBinaryObjT ExponentP;          //!< exponent d mod p-1
    CryptBinaryObjT ExponentQ;          //!< exponent d mod q-1
    CryptBinaryObjT Coefficient;        //!< inverse of q mod p
    char strPrivKeyData[4096];          //!< buffer to hold private key data
} X509PrivateKeyT;

//! defines data for formatting/processing Finished packet
struct SSLFinished
{
    char strLabel[16];
    uint8_t uNextState[2]; // [not resuming, resuming]
};

//! define data for handshake transitions
typedef struct SSLHandshakeMapT
{
    int8_t iCurMsg;
    int8_t iNxtMsg;
} SSLHandshakeMapT;

//! minimal certificate authority data (just enough to validate another certificate)
typedef struct ProtoSSLCACertT
{
    ProtoSSLCertIdentT Subject;     //!< identity of this certificate authority
    uint32_t uFlags;                //!< SSL_CACERTFLAG_*

    int32_t iKeyType;               //!< public key algorithm type
    int32_t iCrvType;               //!< type of curve, if keytype==ecdsa

    int32_t iKeyModSize;            //!< size of public key modulus
    const uint8_t *pKeyModData;     //!< public key modulus for signature verification

    int32_t iKeyExpSize;            //!< size of public key exponent
    uint8_t KeyExpData[16];         //!< public key exponent

    int32_t iMemGroup;              //!< memgroup cert was allocated with (zero == static)
    void   *pMemGroupUserData;      //!< memgroup user data

    X509CertificateT *pX509Cert;    //!< X509 certificate, if this cert has not yet been validated
    struct ProtoSSLCACertT *pNext;  //!< link to next cert in list
} ProtoSSLCACertT;

//! async op function pointer type
typedef int32_t (AsyncOpT)(ProtoSSLRefT *pState);

//! info for async op
typedef struct AsyncOpInfoT
{
    AsyncOpT *pAsyncOp;             //!< async operation to execute, when in ASYNC state
    int32_t iNextState;             //!< next state, when in ASYNC
    int32_t iFailState;             //!< fail state, if async op fails
    int32_t iFailAlert;             //!< fail alert, if async op fails
} AsyncOpInfoT;

//! cipher parameter lookup structure
typedef struct CipherSuiteT
{
    uint16_t uIdent;                //!< two-byte identifier
    uint16_t uMinVers;              //!< minimum required SSL version
    uint8_t uKey;                   //!< key exchange algorithm (SSL3_KEY_*)
    uint8_t uLen;                   //!< key length (SSL3_KEYLEN_*)
    uint8_t uSig;                   //!< signature algorithm (SSL3_SIGALG_*)
    uint8_t uEnc;                   //!< encryption algorithm (SSL3_ENC_*)
    uint8_t uMac;                   //!< MAC digest size (SSL3_MAC_*)
    uint8_t uMacType;               //!< MAC digest type (CRYPTHASH_*)
    uint8_t uVecSize;               //!< explicit IV size
    uint8_t uPrfType;               //!< cipher-suite PRF type (SSL3_PRF_*)
    // no explicit pad here to make static initialization cleaner
    uint32_t uId;                   //!< PROTOSSL_CIPHER_*
    char    strName[66];            //!< cipher name
} CipherSuiteT;

//! elliptic curve used for key exchange
typedef struct EllipticCurveT
{
    uint16_t uIdent;                //!< two-byte identifier
    uint16_t uId;                   //!< PROTOSSL_CURVE_*
    char strName[32];               //!< curve name
} EllipticCurveT;

//! session info, used for tls1.2 and prior resume
typedef struct SessionInfoT
{
    uint16_t uSslVersion;               //!< ssl version used for connection
    uint16_t uCipherId;                 //!< cipher used for connection
    uint8_t SessionId[SSL_SESSID_SIZE]; //!< session id used to identify session
    uint8_t MasterSecret[48];       //!< master secret generated for session
} SessionInfoT;

//! session ticket data (tls1.3 session tickets only, not supported in previous versions)
typedef struct SessionTicketT
{
    CryptHashTypeE eHashType; 
    uint32_t uRecvTime;
    uint32_t uLifetime;
    uint32_t uAgeAdd;
    uint32_t uMaxEarlyDataSize;
    uint16_t uTickLen;
    uint16_t uExtnLen;
    uint8_t  uNonceLen;
    uint8_t  aResumeKey[CRYPTHASH_MAXDIGEST];
    uint8_t  aTicketNonce[SSL3_SESSION_NONCE_MAX];
    uint8_t  aTicketData[SSL3_SESSION_TICKET_MAX];
} SessionTicketT;

//! secure session history, used for ssl resume
typedef struct SessionHistoryT
{
    char strHost[256];              //!< hostname for the session
    uint16_t uPort;                 //!< port for the session
    uint8_t  bSessionTicket;        //!< TRUE if session ticket (tls1.3) else tls1.2-style resume info
    uint8_t  _pad;
    uint32_t uSessionUseTick;       //!< tick when session was last used
    uint32_t uSessionExpTick;       //!< tick when session will expire
    union
    {
        SessionInfoT SessionInfo;
        SessionTicketT SessionTicket;
    };
} SessionHistoryT;

//! certificate validation history, used to skip validation of certificates we've already valdiated
typedef struct CertValidHistoryT
{
    uint32_t uCertValidTick;        //!< tick when certificate was validated
    uint32_t uCertSize;             //!< size of certificate data
    uint8_t FingerprintId[SSL_FINGERPRINT_SIZE]; //!< certificate fingerprint
} CertValidHistoryT;

//! alpn extension protocol information
typedef struct AlpnProtocolT
{
    uint8_t uLength;
    uint8_t _pad[3];
    char strName[256];
} AlpnProtocolT;

//! signature algorithm information
typedef struct SignatureAlgorithmT
{
    uint8_t uHashId;
    uint8_t uSigAlg;
} SignatureAlgorithmT;

//! signature scheme
typedef struct SignatureSchemeT
{
    uint16_t uIdent;                //!< two-byte signature scheme identifier
    SignatureAlgorithmT SigAlg;     //!< hash and signature algorithm used by the signature scheme
    uint8_t uVerifyScheme;          //!< verification algorithm (RSA signature schemes only)
    uint8_t uOidType;               //!< certificate oid used for signature scheme selection
} SignatureSchemeT;

//! signature generate/verify data for async op
typedef struct SignatureVerifyT
{
    const SignatureSchemeT *pSigScheme;
    const X509PrivateKeyT *pPrivateKey;
    uint8_t DsaContext[CRYPTCURVE_DSA_MAXSTATE];
    uint8_t aSigData[SSL_SIG_MAX];
    int32_t iSigSize;
    uint8_t aHashDigest[CRYPTHASH_MAXDIGEST];
    int32_t iHashSize;
    CryptHashTypeE eHashType;
    uint8_t aKeyData[128];
    int32_t iKeySize;
    int32_t iNextState;
    uint8_t bEccContextInitialized;
    uint8_t _pad[3];
} SignatureVerifyT;

//! extra state information required for secure connections (not allocated for unsecure connections)
typedef struct SecureStateT
{
    uint64_t uSendSeqn;             //!< send sequence number
    uint64_t uRecvSeqn;             //!< recv sequence number

    uint32_t uTimer;                //!< base of setup timing

    int32_t iSendProg;              //!< progress within send
    int32_t iSendSize;              //!< total bytes to send

    int32_t iRecvHead;              //!< progress receiving packet header
    int32_t iRecvProg;              //!< progress receiving packet data
    int32_t iRecvSize;              //!< total bytes to receive (tls record size)
    int32_t iRecvBase;              //!< progress decrypting packet data
    int32_t iRecvHshkProg;          //!< progress processing handshake messages
    int32_t iRecvHshkSize;          //!< size of current handshake message being processed (including header)

    const CipherSuiteT *pCipher;    //!< selected cipher suite
    const EllipticCurveT *pEllipticCurve; //!< selected elliptic curve
    const SignatureSchemeT *pSigScheme; //!< selected signature scheme
    uint32_t uSentCiphers;          //!< bitmask of ciphers that were sent in ClientHello
    uint16_t uRetryCipher;          //!< cipher ident sent by HelloRetryRequest (tls1.3)
    uint16_t _pad16;

    SignatureVerifyT SigVerify;

    uint8_t ClientRandom[32];       //!< clients random seed
    uint8_t ServerRandom[32];       //!< servers random seed
    uint8_t SessionId[SSL_SESSID_SIZE]; //!< session id
    uint16_t uSslVersion;           //!< ssl version of connection
    uint16_t uSslClientVersion;     //!< client-requested ssl version (validated in premaster secret)

    uint8_t bSessionResume;         //!< true if session is resumed during handshaking
    uint8_t bSendSecure;            //!< true if sending secure
    uint8_t bRecvSecure;            //!< true if receiving secure
    uint8_t bDateVerifyFailed;      //!< true if date verification of a cert in chain failed
    uint8_t bRSAContextInitialized; //!< true if we have initialized the RSAContext
    uint8_t bEccContextInitialized; //!< true if we have initialized the EccContext
    uint8_t bEccKeyGenerated;       //!< true if an Ecc Public Key has been generated
    uint8_t bSigGenerated;          //!< true if signature source has been generated
    uint8_t bRecvProc;              //!< true if packet has been processed by _RecvPacket()
    uint8_t bHelloRetry;            //!< true if we're in a HelloRetryRequest flow (tls1.3)
    uint8_t bSentCert;              //!< true if a certificate was sent in handshaking
    uint8_t bRecvCert;              //!< true if a certificate was received in handshaking
    uint8_t bSentSessionId;         //!< true if we sent a session identifier in clienthello
    uint8_t bRenegotiationInfo;     //!< true if renegotation info was sent by peer
    uint8_t uPubKeyLength;          //!< length of pubkey used for ECDHE key exchange
    int8_t  iCurMsg;                //!< current handshake message id, used for flow verification

    uint8_t PubKey[256];            //!< peer's public key, used in ECDHE key exchange (note that in the server flow, the server's public key lives here temporarily)
    uint8_t PreMasterKey[48];       //!< pre-master-key
    uint8_t MasterKey[48];          //!< master key
    uint8_t PreSharedKey[CRYPTHASH_MAXDIGEST]; //!< PSK for tls1.3, used in resume flow

    CryptRSAT RSAContext;

    uint8_t KeyBlock[SSL_KEYBLOCK_LEN];          //!< key block
    uint8_t *pServerMAC;            //!< server mac secret
    uint8_t *pClientMAC;            //!< client mac secret
    uint8_t *pServerKey;            //!< server key secret
    uint8_t *pClientKey;            //!< client key secret
    uint8_t *pServerInitVec;        //!< init vector (CBC ciphers)
    uint8_t *pClientInitVec;        //!< init vector (CBC ciphers)
    uint8_t *pServerSecret;         //!< tls1.3 server early/handshake/application/... secret
    uint8_t *pClientSecret;         //!< tls1.3 client early/handshake/application/... secret
    uint8_t *pResumeSecret;         //!< tls1.3 resumption secret

    const uint8_t *pCookie;         //!< pointer to cookie in receive buffer (tls1.3 extension)

    CryptMD5T HandshakeMD5;         //!< MD5 of all handshake data
    CryptSha1T HandshakeSHA;        //!< SHA of all handshake data
    CryptSha2T HandshakeSHA256;     //!< SHA256 of all handshake data
    CryptSha2T HandshakeSHA384;     //!< SHA384 of all handshake data
    CryptSha2T HandshakeSHA512;     //!< SHA512 of all handshake data
    uint8_t aFinishHash[CRYPTHASH_MAXDIGEST]; //!< handshake data storage for tls1.3 flow

    uint8_t aClientHelloHash[CRYPTSHA256_HASHSIZE]; //!< hash of sent clienthello; used in HelloRetryRequest flow

    CryptAesT ReadAes;              //!< aes read cipher state
    CryptAesT WriteAes;             //!< aes write cipher state

    CryptGcmT ReadGcm;              //!< gcm read cipher state
    CryptGcmT WriteGcm;             //!< gcm write cipher state

    CryptChaChaT ReadChaCha;        //!< chacha read cipher state
    CryptChaChaT WriteChaCha;       //!< chacha write cipher state

    X509CertificateT Cert;          //!< the x509 certificate

    uint8_t EccContext[CRYPTCURVE_DH_MAXSTATE]; //!< elliptic curve state

    char strAlpnProtocol[256];      //!< protocol negotiated using the alpn extension

    uint8_t RecvHead[SSL_MIN_PACKET];       //!< buffer to receive the SSL packet header
    uint8_t SendData[SSL_SNDMAX_PACKET];    //!< put at end to make references efficient, include space for debug fence
    uint8_t RecvData[SSL_RCVMAX_PACKET];    //!< put at end to make references efficient, include space for debug fence
} SecureStateT;

//! module state
struct ProtoSSLRefT
{
    SocketT *pSock;                 //!< comm socket
    HostentT *pHost;                //!< host entry

    // module memory group
    int32_t iMemGroup;              //!< module mem group id
    void *pMemGroupUserData;        //!< user data associated with mem group

    NetCritT SecureCrit;            //!< for guarding multithreaded access to secure state

    char strHost[256];              //!< host that we connect to.
    struct sockaddr PeerAddr;       //!< peer info
    struct sockaddr LocalAddr;      //!< cached value of our local address being used, some connections are very short lived making it otherwise difficult to read the local addr info reliably

    AsyncOpInfoT AsyncInfo;         //!< info for async op execution

    int32_t iState;                 //!< protocol state
    int32_t iClosed;                //!< socket closed flag
    SecureStateT *pSecure;          //!< secure state reference
    X509CertificateT *pCertToVal;   //!< server cert to be validated (used in ST_WAIT_CA state)
    ProtoSSLCertInfoT CertInfo;     //!< certificate info (used on failure)
    CertificateDataT *pCertificate; //!< server/client certificate
    char *pPrivateKey;              //!< server/client private key
    int32_t iPrivateKeyLen;         //!< private key length

    uint32_t uEnabledCiphers;       //!< enabled ciphers
    uint32_t uEnabledCurves;        //!< enabled curves
    int32_t iRecvBufSize;           //!< TCP recv buffer size; 0=default
    int32_t iSendBufSize;           //!< TCP send buffer size; 0=default
    int32_t iLastSocketError;       //!< Last socket error before closing the socket
    int32_t iCARequestId;           //!< CA request id (valid if positive)

    int32_t iMaxSendRate;           //!< max send rate (0=uncapped)
    int32_t iMaxRecvRate;           //!< max recv rate (0=uncapped)

    uint16_t uSslVersion;           //!< ssl version application wants us to use
    uint16_t uSslVersionMin;        //!< minimum ssl version application will accept

    uint8_t bAllowAnyCert;          //!< bypass certificate validation
    uint8_t bSessionResumeEnabled;  //!< trueif session resume is enabled (default)
    uint8_t bServer;                //!< true if server, else client
    uint8_t bReuseAddr;             //!< if true set SO_REUSEADDR
    uint8_t bNoDelay;               //!< if true set TCP_NODELAY on the socket
    uint8_t bKeepAlive;             //!< if true override tcp keep-alive (disabled by default)
    int8_t  iClientCertLevel;       //!< 0=no client cert required; 1=client cert requested, 2=client cert required
    uint8_t uHelloExtn;             //!< enabled ClientHello extensions
    int8_t  iCurveDflt;             //!< default elliptic curve to use in ClientHello (tls1.3)
    int8_t  iVerbose;               //!< spam level

    uint8_t bCertInfoSet;           //!< true if cert info has been set
    uint8_t uAlertLevel;            //!< level of most recent alert
    uint8_t uAlertValue;            //!< value of most recent alert
    uint8_t bAlertSent;             //!< true if most recent alert was sent, else false if it was received
    uint8_t _pad[2];

    uint32_t uKeepAliveTime;        //!< tcp keep-alive time; 0=default

    /* for alpn extension;
       on the client: these are the protocol preferences
       on the server: these are the protocols it supports */
    AlpnProtocolT aAlpnProtocols[SSL_ALPN_MAX_PROTOCOLS];
    uint16_t uNumAlpnProtocols;      //!< the number of alpn protocols in the list
    uint16_t uAlpnExtensionLength;   //!< the size of the list we encode in ClientHello (we calculate when we build the list)
};

//! global state
typedef struct ProtoSSLStateT
{
    //! critical section for locking access to state memory
    NetCritT StateCrit;

    //! previous session info, used for secure session share/resume
    SessionHistoryT SessionHistory[SSL_SESSHIST_MAX];

    //! validated certificate info
    CertValidHistoryT CertValidHistory[SSL_CERTVALID_MAX];

    // allocation identifiers
    int32_t iMemGroup;
    void *pMemGroupUserData;

    /* global default settings */

    int32_t iDfltVers;              //!< global version setting
    int32_t iDfltMinVers;           //!< global min version setting
    int32_t iDfltCiph;              //!< global cipher setting
    int32_t iDfltCurves;            //!< global curve setting
}ProtoSSLStateT;

/*** Function Prototypes **********************************************************/

// issue a CA request
static int32_t _ProtoSSLInitiateCARequest(ProtoSSLRefT *pState);

// update recv handshake hash
static void _ProtoSSLRecvHandshakeFinish(ProtoSSLRefT *pState);

// set async execution
static int32_t _ProtoSSLUpdateSetAsyncState(ProtoSSLRefT *pState, AsyncOpT *pAsyncOp, int32_t iNextState, int32_t iFailState, int32_t iFailAlert);

/*** Variables ********************************************************************/

static ProtoSSLStateT *_ProtoSSL_pState = NULL;

//! ServerRandom value that identifies a ServerHello as a HelloRetryRequest 
static const uint8_t _SSL3_HelloRetryRequestRandom[] =
{
    0xcf, 0x21, 0xad, 0x74, 0xe5, 0x9a, 0x61, 0x11, 0xbe, 0x1d, 0x8c, 0x02, 0x1e, 0x65, 0xb8, 0x91,
    0xc2, 0xa2, 0x11, 0x16, 0x7a, 0xbb, 0x8c, 0x5e, 0x07, 0x9e, 0x09, 0xe2, 0xc8, 0xa8, 0x33, 0x9c
};

//! ServerRandom trailing eight bytes that identify an illegitimate downgrade from tls1.3 to tls1.2
static const uint8_t _SSL3_ServerRandomDowngrade12[] =
{
    0x44, 0x4f, 0x57, 0x4e, 0x47, 0x52, 0x44, 0x01
};
//! ServerRandom trailing eight bytes that identify an illegitimate downgrade from tls1.3 to tls1.1 or previous
static const uint8_t _SSL3_ServerRandomDowngrade11[] =
{
    0x44, 0x4f, 0x57, 0x4e, 0x47, 0x52, 0x44, 0x00
};

//! supported ssl cipher suites in order of preference; see http://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-4
static const CipherSuiteT _SSL3_CipherSuite[] =
{
    // TLS1.3 cipher suites
    { 0x1301, SSL3_TLS1_3, SSL3_KEY_NULL,  SSL3_KEYLEN_128,  SSL3_SIGALG_NONE,  SSL3_ENC_GCM,    SSL3_MAC_NULL,   CRYPTHASH_NULL,   12, SSL3_PRF_SHA256, PROTOSSL_CIPHER_AES_128_GCM_SHA256,                        "TLS_AES_128_GCM_SHA256" },                        // TLS1.3 suite 2: gcm128+sha256
    { 0x1302, SSL3_TLS1_3, SSL3_KEY_NULL,  SSL3_KEYLEN_256,  SSL3_SIGALG_NONE,  SSL3_ENC_GCM,    SSL3_MAC_NULL,   CRYPTHASH_NULL,   12, SSL3_PRF_SHA384, PROTOSSL_CIPHER_AES_256_GCM_SHA384,                        "TLS_AES_256_GCM_SHA384" },                        // TLS1.3 suite 1: gcm256+sha384
    { 0x1303, SSL3_TLS1_3, SSL3_KEY_NULL,  SSL3_KEYLEN_256,  SSL3_SIGALG_NONE,  SSL3_ENC_CHACHA, SSL3_MAC_NULL,   CRYPTHASH_NULL,   12, SSL3_PRF_SHA256, PROTOSSL_CIPHER_CHACHA20_POLY1305_SHA256,                  "TLS_CHACHA20_POLY1305_SHA256" },                  // TLS1.3 suite 3: chacha20_poly1305+sha256
    // TLS1.2 AEAD cipher suites
    { 0xc02b, SSL3_TLS1_2, SSL3_KEY_ECDHE, SSL3_KEYLEN_128,  SSL3_SIGALG_ECDSA, SSL3_ENC_GCM,    SSL3_MAC_NULL,   CRYPTHASH_NULL,    4, SSL3_PRF_SHA256, PROTOSSL_CIPHER_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,       "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256" },       // ecdhe+ecdsa+gcm+sha256
    { 0xc02c, SSL3_TLS1_2, SSL3_KEY_ECDHE, SSL3_KEYLEN_256,  SSL3_SIGALG_ECDSA, SSL3_ENC_GCM,    SSL3_MAC_NULL,   CRYPTHASH_NULL,    4, SSL3_PRF_SHA384, PROTOSSL_CIPHER_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,       "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384" },       // ecdhe+ecdsa+gcm+sha384
    { 0xcca9, SSL3_TLS1_2, SSL3_KEY_ECDHE, SSL3_KEYLEN_256,  SSL3_SIGALG_ECDSA, SSL3_ENC_CHACHA, SSL3_MAC_NULL,   CRYPTHASH_NULL,   12, SSL3_PRF_SHA256, PROTOSSL_CIPHER_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256" }, // ecdhe+ecdsa+chacha+sha256
    { 0xc02f, SSL3_TLS1_2, SSL3_KEY_ECDHE, SSL3_KEYLEN_128,  SSL3_SIGALG_RSA,   SSL3_ENC_GCM,    SSL3_MAC_NULL,   CRYPTHASH_NULL,    4, SSL3_PRF_SHA256, PROTOSSL_CIPHER_ECDHE_RSA_WITH_AES_128_GCM_SHA256,         "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256" },         // ecdhe+rsa+gcm+sha256
    { 0xc030, SSL3_TLS1_2, SSL3_KEY_ECDHE, SSL3_KEYLEN_256,  SSL3_SIGALG_RSA,   SSL3_ENC_GCM,    SSL3_MAC_NULL,   CRYPTHASH_NULL,    4, SSL3_PRF_SHA384, PROTOSSL_CIPHER_ECDHE_RSA_WITH_AES_256_GCM_SHA384,         "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384" },         // ecdhe+rsa+gcm+sha384
    { 0xcca8, SSL3_TLS1_2, SSL3_KEY_ECDHE, SSL3_KEYLEN_256,  SSL3_SIGALG_RSA,   SSL3_ENC_CHACHA, SSL3_MAC_NULL,   CRYPTHASH_NULL,   12, SSL3_PRF_SHA256, PROTOSSL_CIPHER_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,   "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256" },   // ecdhe+rsa+chacha+sha256
    { 0x009c, SSL3_TLS1_2, SSL3_KEY_RSA,   SSL3_KEYLEN_128,  SSL3_SIGALG_RSA,   SSL3_ENC_GCM,    SSL3_MAC_NULL,   CRYPTHASH_NULL,    4, SSL3_PRF_SHA256, PROTOSSL_CIPHER_RSA_WITH_AES_128_GCM_SHA256,               "TLS_RSA_WITH_AES_128_GCM_SHA256" },               // suite 156: rsa+gcm+sha256
    { 0x009d, SSL3_TLS1_2, SSL3_KEY_RSA,   SSL3_KEYLEN_256,  SSL3_SIGALG_RSA,   SSL3_ENC_GCM,    SSL3_MAC_NULL,   CRYPTHASH_NULL,    4, SSL3_PRF_SHA384, PROTOSSL_CIPHER_RSA_WITH_AES_256_GCM_SHA384,               "TLS_RSA_WITH_AES_256_GCM_SHA384" },               // suite 157: rsa+gcm+sha384
    // TLS1.2 cipher suites
    { 0xc023, SSL3_TLS1_2, SSL3_KEY_ECDHE, SSL3_KEYLEN_128,  SSL3_SIGALG_ECDSA, SSL3_ENC_AES,    SSL3_MAC_SHA256, CRYPTHASH_SHA256, 16, SSL3_PRF_SHA256, PROTOSSL_CIPHER_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,       "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256" },       // ecdhe+ecdsa+aes+sha256
    { 0xc024, SSL3_TLS1_2, SSL3_KEY_ECDHE, SSL3_KEYLEN_256,  SSL3_SIGALG_ECDSA, SSL3_ENC_AES,    SSL3_MAC_SHA384, CRYPTHASH_SHA384, 16, SSL3_PRF_SHA384, PROTOSSL_CIPHER_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384,       "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384" },       // ecdhe+ecdsa+aes+sha384
    { 0xc027, SSL3_TLS1_2, SSL3_KEY_ECDHE, SSL3_KEYLEN_128,  SSL3_SIGALG_RSA,   SSL3_ENC_AES,    SSL3_MAC_SHA256, CRYPTHASH_SHA256, 16, SSL3_PRF_SHA256, PROTOSSL_CIPHER_ECDHE_RSA_WITH_AES_128_CBC_SHA256,         "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256" },         // ecdhe+rsa+aes+sha256
    { 0xc028, SSL3_TLS1_2, SSL3_KEY_ECDHE, SSL3_KEYLEN_256,  SSL3_SIGALG_RSA,   SSL3_ENC_AES,    SSL3_MAC_SHA384, CRYPTHASH_SHA384, 16, SSL3_PRF_SHA384, PROTOSSL_CIPHER_ECDHE_RSA_WITH_AES_256_CBC_SHA384,         "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384" },         // ecdhe+rsa+aes+sha384
    { 0x003c, SSL3_TLS1_2, SSL3_KEY_RSA,   SSL3_KEYLEN_128,  SSL3_SIGALG_RSA,   SSL3_ENC_AES,    SSL3_MAC_SHA256, CRYPTHASH_SHA256, 16, SSL3_PRF_SHA256, PROTOSSL_CIPHER_RSA_WITH_AES_128_CBC_SHA256,               "TLS_RSA_WITH_AES_128_CBC_SHA256" },               // suite 60: rsa+aes+sha256
    { 0x003d, SSL3_TLS1_2, SSL3_KEY_RSA,   SSL3_KEYLEN_256,  SSL3_SIGALG_RSA,   SSL3_ENC_AES,    SSL3_MAC_SHA256, CRYPTHASH_SHA256, 16, SSL3_PRF_SHA256, PROTOSSL_CIPHER_RSA_WITH_AES_256_CBC_SHA256,               "TLS_RSA_WITH_AES_256_CBC_SHA256" },               // suite 61: rsa+aes+sha256
    // TLS1.0 cipher suites
    { 0xc009, SSL3_TLS1_0, SSL3_KEY_ECDHE, SSL3_KEYLEN_128,  SSL3_SIGALG_ECDSA, SSL3_ENC_AES,    SSL3_MAC_SHA,    CRYPTHASH_SHA1,   16, SSL3_PRF_SHA256, PROTOSSL_CIPHER_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,          "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA" },          // ecdhe+ecdsa+aes+sha
    { 0xc00a, SSL3_TLS1_0, SSL3_KEY_ECDHE, SSL3_KEYLEN_256,  SSL3_SIGALG_ECDSA, SSL3_ENC_AES,    SSL3_MAC_SHA,    CRYPTHASH_SHA1,   16, SSL3_PRF_SHA256, PROTOSSL_CIPHER_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,          "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA" },          // ecdhe+ecdsa+aes+sha
    { 0xc013, SSL3_TLS1_0, SSL3_KEY_ECDHE, SSL3_KEYLEN_128,  SSL3_SIGALG_RSA,   SSL3_ENC_AES,    SSL3_MAC_SHA,    CRYPTHASH_SHA1,   16, SSL3_PRF_SHA256, PROTOSSL_CIPHER_ECDHE_RSA_WITH_AES_128_CBC_SHA,            "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA" },            // ecdhe+rsa+aes+sha
    { 0xc014, SSL3_TLS1_0, SSL3_KEY_ECDHE, SSL3_KEYLEN_256,  SSL3_SIGALG_RSA,   SSL3_ENC_AES,    SSL3_MAC_SHA,    CRYPTHASH_SHA1,   16, SSL3_PRF_SHA256, PROTOSSL_CIPHER_ECDHE_RSA_WITH_AES_256_CBC_SHA,            "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA" },            // ecdhe+rsa+aes+sha
    // SSLv3 cipher suites
    { 0x002f, SSL3_SSLv3,  SSL3_KEY_RSA,   SSL3_KEYLEN_128,  SSL3_SIGALG_RSA,   SSL3_ENC_AES,    SSL3_MAC_SHA,    CRYPTHASH_SHA1,   16, SSL3_PRF_SHA256, PROTOSSL_CIPHER_RSA_WITH_AES_128_CBC_SHA,                  "TLS_RSA_WITH_AES_128_CBC_SHA" },                  // suite 47: rsa+aes+sha
    { 0x0035, SSL3_SSLv3,  SSL3_KEY_RSA,   SSL3_KEYLEN_256,  SSL3_SIGALG_RSA,   SSL3_ENC_AES,    SSL3_MAC_SHA,    CRYPTHASH_SHA1,   16, SSL3_PRF_SHA256, PROTOSSL_CIPHER_RSA_WITH_AES_256_CBC_SHA,                  "TLS_RSA_WITH_AES_256_CBC_SHA" },                  // suite 53: rsa+aes+sha
};

//! supported elliptic curves; see http://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-8
static const EllipticCurveT _SSL3_EllipticCurves[SSL3_NUM_CURVES] =
{
    // curve 23: P-256
    { CRYPTCURVE_SECP256R1, PROTOSSL_CURVE_SECP256R1,   "SECP256R1" },
    // curve 29: X25519
    { CRYPTCURVE_X25519,    PROTOSSL_CURVE_X25519,      "X25519" },
    // curve 24: P-384
    { CRYPTCURVE_SECP384R1, PROTOSSL_CURVE_SECP384R1,   "SECP384R1" },
    // curve 30: X448
    { CRYPTCURVE_X448,      PROTOSSL_CURVE_X448,        "X448" }
};

//! signature schemes; see https://tools.ietf.org/html/rfc8446#section-4.2.3
static const SignatureSchemeT _SSL3_SignatureSchemes[] =
{
    { SSL3_SIGSCHEME_ECDSA_SHA256,        { SSL3_HASHID_SHA256, SSL3_SIGALG_ECDSA }, SSL3_SIGVERIFY_NONE,      ASN_OBJ_ECDSA_SECP256R1 },
    { SSL3_SIGSCHEME_RSA_PSS_PSS_SHA256,  { SSL3_HASHID_SHA256, SSL3_SIGALG_RSA   }, SSL3_SIGVERIFY_RSA_PSS,   ASN_OBJ_RSASSA_PSS      },
    { SSL3_SIGSCHEME_RSA_PSS_RSAE_SHA256, { SSL3_HASHID_SHA256, SSL3_SIGALG_RSA   }, SSL3_SIGVERIFY_RSA_PSS,   ASN_OBJ_RSA_PKCS_KEY    },
    { SSL3_SIGSCHEME_RSA_PKCS1_SHA256,    { SSL3_HASHID_SHA256, SSL3_SIGALG_RSA   }, SSL3_SIGVERIFY_RSA_PKCS1, ASN_OBJ_RSA_PKCS_KEY    },
    { SSL3_SIGSCHEME_ECDSA_SHA384,        { SSL3_HASHID_SHA384, SSL3_SIGALG_ECDSA }, SSL3_SIGVERIFY_NONE,      ASN_OBJ_ECDSA_SECP384R1 },
    { SSL3_SIGSCHEME_RSA_PSS_PSS_SHA384,  { SSL3_HASHID_SHA384, SSL3_SIGALG_RSA   }, SSL3_SIGVERIFY_RSA_PSS,   ASN_OBJ_RSASSA_PSS      },
    { SSL3_SIGSCHEME_RSA_PSS_RSAE_SHA384, { SSL3_HASHID_SHA384, SSL3_SIGALG_RSA   }, SSL3_SIGVERIFY_RSA_PSS,   ASN_OBJ_RSA_PKCS_KEY    },
    { SSL3_SIGSCHEME_RSA_PKCS1_SHA384,    { SSL3_HASHID_SHA384, SSL3_SIGALG_RSA   }, SSL3_SIGVERIFY_RSA_PKCS1, ASN_OBJ_RSA_PKCS_KEY    },
    { SSL3_SIGSCHEME_RSA_PSS_PSS_SHA512,  { SSL3_HASHID_SHA512, SSL3_SIGALG_RSA   }, SSL3_SIGVERIFY_RSA_PSS,   ASN_OBJ_RSASSA_PSS      },
    { SSL3_SIGSCHEME_RSA_PSS_RSAE_SHA512, { SSL3_HASHID_SHA512, SSL3_SIGALG_RSA   }, SSL3_SIGVERIFY_RSA_PSS,   ASN_OBJ_RSA_PKCS_KEY    },
    { SSL3_SIGSCHEME_RSA_PKCS1_SHA512,    { SSL3_HASHID_SHA512, SSL3_SIGALG_RSA   }, SSL3_SIGVERIFY_RSA_PKCS1, ASN_OBJ_RSA_PKCS_KEY    },
    { SSL3_SIGSCHEME_ECDSA_SHA1,          { SSL3_HASHID_SHA1,   SSL3_SIGALG_ECDSA }, SSL3_SIGVERIFY_NONE,      ASN_OBJ_ECDSA_KEY       },
    { SSL3_SIGSCHEME_RSA_PKCS1_SHA1,      { SSL3_HASHID_SHA1,   SSL3_SIGALG_RSA   }, SSL3_SIGVERIFY_RSA_PKCS1, ASN_OBJ_RSA_PKCS_KEY    }
};

//! handshake transition validation map for TLS1.2 and prior clients; see https://tools.ietf.org/html/rfc5246#section-7.3
static const SSLHandshakeMapT _SSL3_ClientRecvMsgMap[] =
{
    { SSL3_MSG_CLIENT_HELLO, SSL3_MSG_SERVER_HELLO },
    { SSL3_MSG_SERVER_HELLO, SSL3_MSG_CERTIFICATE },
    { SSL3_MSG_SERVER_HELLO, SSL3_MSG_FINISHED }, 
    { SSL3_MSG_CERTIFICATE,  SSL3_MSG_SERVER_KEY },
    { SSL3_MSG_CERTIFICATE,  SSL3_MSG_CERT_REQ },
    { SSL3_MSG_CERTIFICATE,  SSL3_MSG_SERVER_DONE },
    { SSL3_MSG_SERVER_KEY,   SSL3_MSG_CERT_REQ },
    { SSL3_MSG_SERVER_KEY,   SSL3_MSG_SERVER_DONE },
    { SSL3_MSG_CERT_REQ,     SSL3_MSG_SERVER_DONE },
    { SSL3_MSG_SERVER_DONE,  SSL3_MSG_FINISHED },
    { -1,                    -1 }
};
//! handshake transition validation map for TLS1.2 and prior servers
static const SSLHandshakeMapT _SSL3_ServerRecvMsgMap[] =
{
    { 0,                      SSL3_MSG_CLIENT_HELLO },
    { SSL3_MSG_CLIENT_HELLO,  SSL3_MSG_CERTIFICATE },
    { SSL3_MSG_CLIENT_HELLO,  SSL3_MSG_CLIENT_KEY },
    { SSL3_MSG_CLIENT_HELLO,  SSL3_MSG_FINISHED },
    { SSL3_MSG_CERTIFICATE,   SSL3_MSG_CLIENT_KEY },
    { SSL3_MSG_CLIENT_KEY,    SSL3_MSG_CERT_VERIFY },
    { SSL3_MSG_CLIENT_KEY,    SSL3_MSG_FINISHED },
    { SSL3_MSG_CERT_VERIFY,   SSL3_MSG_FINISHED },
    { -1,                     -1 }
};

//! handshake transition validation map for TLS1.3 clients; see https://tools.ietf.org/html/rfc8446#section-2
static const SSLHandshakeMapT _SSL3_ClientRecvMsgMap_13[] =
{
    { SSL3_MSG_CLIENT_HELLO,         SSL3_MSG_SERVER_HELLO },
    { SSL3_MSG_SERVER_HELLO,         SSL3_MSG_ENCRYPTED_EXTENSIONS },
    { SSL3_MSG_SERVER_HELLO,         SSL3_MSG_FINISHED }, 
    { SSL3_MSG_ENCRYPTED_EXTENSIONS, SSL3_MSG_CERT_REQ }, 
    { SSL3_MSG_ENCRYPTED_EXTENSIONS, SSL3_MSG_CERTIFICATE }, 
    { SSL3_MSG_ENCRYPTED_EXTENSIONS, SSL3_MSG_FINISHED }, 
    { SSL3_MSG_CERT_REQ,             SSL3_MSG_CERTIFICATE }, 
    { SSL3_MSG_CERTIFICATE,          SSL3_MSG_CERT_VERIFY }, 
    { SSL3_MSG_CERT_VERIFY,          SSL3_MSG_FINISHED },
    { SSL3_MSG_FINISHED,             SSL3_MSG_NEW_SESSION_TICKET },
    { SSL3_MSG_NEW_SESSION_TICKET,   SSL3_MSG_NEW_SESSION_TICKET },
    { -1,                            -1 }
};
//! handshake transition validation map for TLS1.3 servers
static const SSLHandshakeMapT _SSL3_ServerRecvMsgMap_13[] =
{
    { 0,                             SSL3_MSG_CLIENT_HELLO },
    { SSL3_MSG_CLIENT_HELLO,         SSL3_MSG_CLIENT_HELLO },
    { SSL3_MSG_CLIENT_HELLO,         SSL3_MSG_CERTIFICATE },
    { SSL3_MSG_CLIENT_HELLO,         SSL3_MSG_FINISHED },
    { SSL3_MSG_CERTIFICATE,          SSL3_MSG_CERT_VERIFY }, 
    { SSL3_MSG_CERTIFICATE,          SSL3_MSG_FINISHED }, 
    { SSL3_MSG_CERT_VERIFY,          SSL3_MSG_FINISHED },
    { -1,                            -1 }
};

//! .pem certificate signature identifiers; see https://tools.ietf.org/html/rfc7468
static const CertificateSignatureT _SSL3_CertSignatures[] =
{
    { "-----BEGIN CERTIFICATE-----",      "-----END CERTIFICATE-----",      ASN_OBJ_RSA_PKCS_KEY },     // pkcs#1 certificate
    { "-----BEGIN X509 CERTIFICATE-----", "-----END X509 CERTIFICATE-----", ASN_OBJ_RSA_PKCS_KEY },     // pkcs#1 certificate (alternate/obsolete form)
    { "-----BEGIN RSA PRIVATE KEY-----",  "-----END RSA PRIVATE KEY-----",  ASN_OBJ_RSA_PKCS_KEY },     // pkcs#1 private key
    { "-----BEGIN PRIVATE KEY-----",      "-----END PRIVATE KEY-----",      ASN_OBJ_RSASSA_PSS   },     // pkcs#8 private key (rsa-pss)
    { "-----BEGIN EC PRIVATE KEY-----",   "-----END EC PRIVATE KEY-----",   ASN_OBJ_ECDSA_KEY    }      // pkcs#8 private key (ecdsa)
};

//! translation table for SSL3_HASHID_* to CryptHashE type
static const CryptHashTypeE _SSL3_HashIdToCrypt[SSL3_HASHID_MAX+1] =
{
    CRYPTHASH_NULL,     // SSL3_HASHID_NONE
    CRYPTHASH_MD5,      // SSL3_HASHID_MD5
    CRYPTHASH_SHA1,     // SSL3_HASHID_SHA1
    CRYPTHASH_SHA224,   // SSL3_HASHID_SHA224
    CRYPTHASH_SHA256,   // SSL3_HASHID_SHA256
    CRYPTHASH_SHA384,   // SSL3_HASHID_SHA384
    CRYPTHASH_SHA512    // SSL3_hashid_sha512
};

//! translation table for SSL3_HASHID_* to ASN hash object identifier; ASN_OBJ_NONE means unsupported
static const uint32_t _SSL3_CrypttoASN[CRYPTHASH_NUMHASHES] =
{
    ASN_OBJ_NONE,       // CRYPTHASH_NULL
    ASN_OBJ_NONE,       // CRYPTHASH_MURMUR3
    ASN_OBJ_MD5,        // CRYPTHASH_MD5
    ASN_OBJ_SHA1,       // CRYPTHASH_SHA1
    ASN_OBJ_NONE,       // CRYPTHASH_SHA224
    ASN_OBJ_SHA256,     // CRYPTHASH_SHA256
    ASN_OBJ_SHA384,     // CRYPTHASH_SHA384
    ASN_OBJ_SHA512      // CRYPTHASH_SHA512
};

//! ssl version name table
static const char *_SSL3_strVersionNames[] =
{
    "SSLv3",            // defined here to retain easy version number to name translation, do not remove
    "TLSv1",
    "TLSv1.1",
    "TLSv1.2",
    "TLSv1.3"
};

// signature types - subtract type from ASN_OBJ_RSA_PKCS_MD5 to get offset for this table
static const char *_SSL3_strSignatureTypes[] =
{
    "md5WithRSAEncryption",
    "sha1WithRSAEncryption",
    "sha256WithRSAEncryption",
    "sha384WithRSAEncryption",
    "sha512WithRSAEncryption",
    "ecdsa-with-SHA256",
    "ecdsa-with-SHA384",
    "ecdsa-with-SHA512"
};

#if DIRTYCODE_LOGGING
#if DEBUG_ALL_OBJS
static const char *_SSL3_strAsnTypes[] =
{
    "ASN_00",
    "ASN_TYPE_BOOLEAN",         // 0x01
    "ASN_TYPE_INTEGER",         // 0x02
    "ASN_TYPE_BITSTRING",       // 0x03
    "ASN_TYPE_OCTSTRING",       // 0x04
    "ASN_TYPE_NULL",            // 0x05
    "ASN_TYPE_OBJECT",          // 0x06
    "ASN_07", "ASN_08", "ASN_09",
    "ASN_0A", "ASN_0B",
    "ASN_TYPE_UTF8STR",         // 0x0c
    "ASN_0D", "ASN_0E", "ASN_0F",
    "ASN_TYPE_SEQN",            // 0x10
    "ASN_TYPE_SET",             // 0x11
    "ASN_12",                   // 0x12
    "ASN_TYPE_PRINTSTR",        // 0x13
    "ASN_TYPE_T61",             // 0x14 (Teletex string)
    "ASN_15",
    "ASN_TYPE_IA5",             // 0x16 (IA5 string)
    "ASN_TYPE_UTCTIME",         // 0x17
    "ASN_TYPE_GENERALIZEDTIME", // 0x18
    "ASN_19", "ASN_1A", "ASN_1B",
    "ASN_1C", "ASN_1D",
    "ASN_TYPE_UNICODESTR",      // 0x1e
    "ASN_1F",
};
static const char *_SSL3_strAsnObjs[ASN_OBJ_COUNT] =
{
    "ASN_OBJ_NONE",
    "ASN_OBJ_COUNTRY",
    "ASN_OBJ_STATE",
    "ASN_OBJ_CITY",
    "ASN_OBJ_ORGANIZATION",
    "ASN_OBJ_UNIT",
    "ASN_OBJ_COMMON",
    "ASN_OBJ_SUBJECT_ALT",
    "ASN_OBJ_BASIC_CONSTRAINTS",
    "ASN_OBJ_MD5",
    "ASN_OBJ_SHA1",
    "ASN_OBJ_SHA256",
    "ASN_OBJ_SHA384",
    "ASN_OBJ_SHA512",
    "ASN_OBJ_RSA_PKCS_KEY",
    "ASN_OBJ_ECDSA_KEY",
    "ASN_OBJ_RSA_PKCS_MD5",
    "ASN_OBJ_RSA_PKCS_SHA1",
    "ASN_OBJ_RSA_PKCS_SHA256",
    "ASN_OBJ_RSA_PKCS_SHA384",
    "ASN_OBJ_RSA_PKCS_SHA512",
    "ASN_OBJ_RSASSA_PSS",
    "ASN_OBJ_ECDSA_SHA256",
    "ASN_OBJ_ECDSA_SHA384",
    "ASN_OBJ_ECDSA_SHA512",
    "ASN_OBJ_ECDSA_SECP256R1",
    "ASN_OBJ_ECDSA_SECP384R1",
    "ASN_OBJ_ECDSA_SECP512R1",
    "ASN_OBJ_PKCS1_MGF1"
};
#endif // DEBUG_ALL_OBJS

// tls extension names
static const char *_SSL3_strExtensionNames[] =
{
    "server_name",
    "max_fragment_length",
    "client_certificate_url",
    "trusted_ca_keys",
    "truncated_hmac",
    "status_request",
    "user_mapping",
    "client_authz",
    "server_authz",
    "cert_type",
    "supported_groups", // (renamed from "elliptic_curves")
    "ec_point_formats",
    "srp",
    "signature_algorithms",
    "use_srtp",
    "heartbeat",
    "application_layer_protocol_negotiation",
    "status_request_v2",
    "signed_certificate_timestamp",
    "client_certificate_type",
    "server_certificate_type",
    "padding",
    "encrypt_then_mac",
    "extended_master_secret",
    "token_binding",
    "cached_info",
    "unassigned", "unassigned", "unassigned", "unassigned", "unassigned", "unassigned", "unassigned", "unassigned", "unassigned",
    "SessionTicket TLS",
    "unassigned", "unassigned", "unassigned", "unassigned", "unassigned",
    "pre_shared_key",
    "early_data",
    "supported_versions",
    "cookie",
    "psk_key_exchange_modes",
    "unassigned",
    "certificate_authorities",
    "oid_filter",
    "post_handshake_auth",
    "signature_algorithms_cert",
    "key_share",
};

#endif // DIRTYCODE_LOGGING

//! alert description table; see https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml
static const ProtoSSLAlertDescT _ProtoSSL_AlertList[] =
{
    { SSL3_ALERT_DESC_CLOSE_NOTIFY,               "close notify"              },    //   0
    { SSL3_ALERT_DESC_UNEXPECTED_MESSAGE,         "unexpected message"        },    //  10
    { SSL3_ALERT_DESC_BAD_RECORD_MAC,             "bad record mac"            },    //  20
    { SSL3_ALERT_DESC_DECRYPTION_FAILED,          "decryption failed"         },    //  21 - reserved
    { SSL3_ALERT_DESC_RECORD_OVERFLOW,            "record overflow"           },    //  22
    { SSL3_ALERT_DESC_DECOMPRESSION_FAILURE,      "decompression failure"     },    //  30 - reserved
    { SSL3_ALERT_DESC_HANDSHAKE_FAILURE,          "handshake failure"         },    //  40
    { SSL3_ALERT_DESC_NO_CERTIFICATE,             "no certificate"            },    //  41 - reserved
    { SSL3_ALERT_DESC_BAD_CERTFICIATE,            "bad certificate"           },    //  42
    { SSL3_ALERT_DESC_UNSUPPORTED_CERTIFICATE,    "unsupported certificate"   },    //  43
    { SSL3_ALERT_DESC_CERTIFICATE_REVOKED,        "certificate revoked"       },    //  44
    { SSL3_ALERT_DESC_CERTIFICATE_EXPIRED,        "certificate expired"       },    //  45
    { SSL3_ALERT_DESC_CERTIFICATE_UNKNOWN,        "certificate unknown"       },    //  46
    { SSL3_ALERT_DESC_ILLEGAL_PARAMETER,          "illegal parameter"         },    //  47
    // the following alert types are all TLS only
    { SSL3_ALERT_DESC_UNKNOWN_CA,                 "unknown ca"                },    //  48
    { SSL3_ALERT_DESC_ACCESS_DENIED,              "access denied"             },    //  49
    { SSL3_ALERT_DESC_DECODE_ERROR,               "decode error"              },    //  50
    { SSL3_ALERT_DESC_DECRYPT_ERROR,              "decrypt error"             },    //  51
    { SSL3_ALERT_DESC_EXPORT_RESTRICTION,         "export restriction"        },    //  60 - reserved
    { SSL3_ALERT_DESC_PROTOCOL_VERSION,           "protocol version"          },    //  70
    { SSL3_ALERT_DESC_INSUFFICIENT_SECURITY,      "insufficient security"     },    //  71
    { SSL3_ALERT_DESC_INTERNAL_ERROR,             "internal error"            },    //  80
    { SSL3_ALERT_DESC_INAPPROPRIATE_FALLBACK,     "inappropriate fallback"    },    //  86
    { SSL3_ALERT_DESC_USER_CANCELLED,             "user cancelled"            },    //  90
    { SSL3_ALERT_DESC_NO_RENEGOTIATION,           "no renegotiation"          },    // 100 - reserved
    { SSL3_ALERT_DESC_MISSING_EXTENSION,          "missing extension"         },    // 109
    { SSL3_ALERT_DESC_UNSUPPORTED_EXTENSION,      "unsupported extension"     },    // 110
    // alert extensions; see http://tools.ietf.org/html/rfc6066#section-9
    { SSL3_ALERT_DESC_CERTIFICATE_UNOBTAINABLE,   "certificate unobtainable"  },    // 111
    { SSL3_ALERT_DESC_UNRECOGNIZED_NAME,          "unrecognized name"         },    // 112
    { SSL3_ALERT_DESC_BAD_CERTIFICATE_STATUS,     "bad certificate status"    },    // 113
    { SSL3_ALERT_DESC_BAD_CERTIFICATE_HASH,       "bad certificate hash"      },    // 114
    // alert extension; see http://tools.ietf.org/html/rfc4279#section-6
    { SSL3_ALERT_DESC_UNKNOWN_PSK_IDENTITY,       "unknown psk identify"      },    // 115
    // alert extension; see https://tools.ietf.org/html/rfc8446#section-4.4.2.4
    { SSL3_ALERT_DESC_CERTIFICATE_REQUIRED,       "certificate required"      },    // 116
    // alert extension; see http://tools.ietf.org/html/rfc7301#section-3.2
    { SSL3_ALERT_DESC_NO_APPLICATION_PROTOCOL,    "no application protocol"   },    // 120
    { -1, NULL                                                                },    // list terminator
};

// ASN object identification table
static const struct ASNObjectT _SSL_ObjectList[] =
{
    { ASN_OBJ_COUNTRY, 3, { 0x55, 0x04, 0x06 } },
    { ASN_OBJ_CITY, 3, { 0x55, 0x04, 0x07 } },
    { ASN_OBJ_STATE, 3, { 0x55, 0x04, 0x08 } },
    { ASN_OBJ_ORGANIZATION, 3, { 0x55, 0x04, 0x0a } },
    { ASN_OBJ_UNIT, 3, { 0x55, 0x04, 0x0b } },
    { ASN_OBJ_COMMON, 3, { 0x55, 0x04, 0x03 } },
    { ASN_OBJ_SUBJECT_ALT, 3, { 0x55, 0x1d, 0x11 } },
    { ASN_OBJ_BASIC_CONSTRAINTS, 3, { 0x55, 0x1d, 0x13 } },

    // OBJ_md5 - OID 1.2.840.113549.2.5
    { ASN_OBJ_MD5, 8, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x05 } },
    // OBJ_sha1 - OID 1.3.14.3.2.26
    { ASN_OBJ_SHA1, 5, { 0x2b, 0x0e, 0x03, 0x02, 0x1a } },
    // OBJ_sha256 - OID 2.16.840.1.101.3.4.2.1
    { ASN_OBJ_SHA256, 9, { 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01 } },
    // OBJ_sha384 - OID 2.16.840.1.101.3.4.2.2
    { ASN_OBJ_SHA384, 9, { 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02 } },
    // OBJ_sha512 - OID 2.16.840.1.101.3.4.2.3
    { ASN_OBJ_SHA512, 9, { 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03 } },

    // RSA (PKCS #1 v1.5) key transport algorithm, OID 1.2.840.113349.1.1.1
    { ASN_OBJ_RSA_PKCS_KEY, 9, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01 } },
    // RSA (PKCS #1 v1.5) with MD5 signature, OID 1.2.840.113549.1.1.4
    { ASN_OBJ_RSA_PKCS_MD5, 9, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x04 } },
    // RSA (PKCS #1 v1.5) with SHA-1 signature; sha1withRSAEncryption OID 1.2.840.113549.1.1.5
    { ASN_OBJ_RSA_PKCS_SHA1, 9, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05 } },
    /* the following are obsolete alternate definitions of sha1withRSAEncryption; we define them
       here for compatibility, because some certificates are still generated with these ids
       (by makecert.exe, included with WindowsSDK, for example) */
    // RSA (PKCS #1 v1.5) with SHA-1 signature; sha-1WithRSAEncryption (obsolete) OID 1.3.14.3.2.29
    { ASN_OBJ_RSA_PKCS_SHA1, 5, { 0x2b, 0x0e, 0x03, 0x02, 0x1d } },
    // RSA (PKCS #1 v1.5) with SHA-1 signature; rsaSignatureWithsha1 (obsolete) OID 1.3.36.3.3.1.2
    { ASN_OBJ_RSA_PKCS_SHA1, 5, { 0x2b, 0x24, 0x03, 0x03, 0x01, 0x02 } },

    // PKCS-MGF1 1.2.840.113549.1.1.8
    { ASN_OBJ_PKCS1_MGF1, 9, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x08 } },
    // RSASSA-PSS PKCS#1 1.2.840.113549.1.1.10
    { ASN_OBJ_RSASSA_PSS, 9, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0a } },

    /* sha2+rsa combinations */
    // RSA (PKCS #1 v1.5) with SHA-256 signature; sha256withRSAEncryption OID 1.2.840.113549.1.1.11
    { ASN_OBJ_RSA_PKCS_SHA256, 9, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b } },
    // RSA (PKCS #1 v1.5) with SHA-384 signature; sha384withRSAEncryption OID 1.2.840.113549.1.1.12
    { ASN_OBJ_RSA_PKCS_SHA384, 9, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0c } },
    // RSA (PKCS #1 v1.5) with SHA-512 signature; sha512withRSAEncryption OID 1.2.840.113549.1.1.13
    { ASN_OBJ_RSA_PKCS_SHA512, 9, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0d } },

    // ecdsa key PKCS#8 1.2.840.10045.2.1
    { ASN_OBJ_ECDSA_KEY, 7, { 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01 } },
    // prime256v1/secp256r1 1.2.840.10045.3.1.7
    { ASN_OBJ_ECDSA_SECP256R1, 8, {0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07 } },
    // ecdsa-with-SHA256 1.2.840.10045.4.3.2
    { ASN_OBJ_ECDSA_SHA256, 8, {0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02 } },
    // ecdsa-with-SHA384 1.2.840.10045.4.3.3
    { ASN_OBJ_ECDSA_SHA384, 8, {0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x03 } },
    // ecdsa-with-SHA512 1.2.840.10045.4.3.4
    { ASN_OBJ_ECDSA_SHA512, 8, {0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x04 } },

    // prime384v1/secp384r1 1.3.132.0.34
    { ASN_OBJ_ECDSA_SECP384R1, 5, {0x2b, 0x81, 0x04, 0x00, 0x22 } },
    // prime521v1/secp521r1 1.3.132.0.35
    { ASN_OBJ_ECDSA_SECP521R1, 5, {0x2b, 0x81, 0x04, 0x00, 0x23 } },

    // array terminator
    { ASN_OBJ_NONE, 0, { 0 } }
};

// The 2048-bit public key modulus for 2015 GOS CA Cert signed with sha256 and an exponent of 65537
static const uint8_t _ProtoSSL_GOS2015ServerModulus2048[] =
{
    0xcc, 0x6d, 0x54, 0xb6, 0xf4, 0xe4, 0x84, 0xe7, 0x20, 0x76, 0x02, 0xd7, 0x97, 0x48, 0x75, 0x7e,
    0x7e, 0xfb, 0x43, 0x9c, 0x3d, 0xa1, 0x96, 0x47, 0xc1, 0x5d, 0x07, 0x8b, 0x30, 0x73, 0xbf, 0x9d,
    0xfe, 0x75, 0x94, 0x55, 0x21, 0xd0, 0x88, 0x74, 0x66, 0x4c, 0xa2, 0xb7, 0xfe, 0x9f, 0xc0, 0x3b,
    0xf0, 0x60, 0xa0, 0xdb, 0x08, 0x33, 0x2b, 0x6e, 0xf8, 0x02, 0x05, 0xb9, 0x87, 0x9d, 0xac, 0x65,
    0xd5, 0x06, 0x9d, 0x05, 0xe8, 0xd1, 0xb6, 0xf5, 0xde, 0x7d, 0xa5, 0xa4, 0x7d, 0x8a, 0xcb, 0x99,
    0x31, 0xb6, 0x85, 0x9b, 0xa2, 0xce, 0x39, 0xe2, 0x8c, 0x65, 0xaa, 0x07, 0xfc, 0x15, 0x33, 0x07,
    0x00, 0xd1, 0x72, 0x15, 0x13, 0x0d, 0x87, 0x0f, 0x5c, 0xa2, 0x5e, 0xd0, 0xd5, 0xbf, 0xd9, 0x03,
    0x32, 0x62, 0xaf, 0xf5, 0xef, 0x53, 0x36, 0xa8, 0x34, 0xda, 0xb6, 0xa3, 0xec, 0x5c, 0x6a, 0xc0,
    0x67, 0xf8, 0xbe, 0x37, 0x9f, 0xb3, 0xc8, 0x2d, 0xf0, 0x36, 0x4a, 0x6f, 0x6b, 0x06, 0xee, 0xb7,
    0x85, 0xf2, 0x7f, 0x73, 0x6c, 0x01, 0x84, 0x83, 0xe4, 0xda, 0x46, 0xd0, 0x23, 0x9a, 0x6d, 0xf1,
    0x77, 0x7c, 0x05, 0x81, 0x90, 0x4f, 0x6a, 0x44, 0x83, 0x78, 0x3b, 0x71, 0xad, 0x12, 0xc0, 0x48,
    0xc8, 0x73, 0x89, 0xf1, 0x98, 0x78, 0x7b, 0xb4, 0x08, 0x4a, 0xba, 0xe8, 0x59, 0x57, 0xe2, 0xfc,
    0x29, 0xac, 0xbf, 0xf5, 0xa2, 0x9d, 0x4f, 0x2c, 0x64, 0xdc, 0xd7, 0x92, 0x19, 0x1c, 0xc5, 0xfa,
    0xdb, 0x92, 0xc0, 0x90, 0x4b, 0xa8, 0xe9, 0xf2, 0x0d, 0x94, 0x1a, 0xb2, 0x5f, 0xdd, 0x33, 0xae,
    0xff, 0x66, 0x90, 0x97, 0xb2, 0xa8, 0xa5, 0x1b, 0xfa, 0x6f, 0x41, 0xb2, 0x84, 0xba, 0x52, 0x34,
    0x97, 0x4a, 0xd3, 0xc7, 0xb2, 0x3f, 0xdd, 0xdb, 0xc9, 0xb1, 0x13, 0x82, 0x77, 0xe8, 0x6a, 0xcd
};

// the 256 bit public key curve data for 2019 GS CA cert signed with sha384
static const uint8_t _ProtoSSL_GS2019ServerKey[] =
{
    0x04, 0x13, 0x3f, 0x21, 0x93, 0x86, 0xf7, 0x65, 0xc4, 0x7f, 0x8c, 0x1c, 0xef, 0x49, 0xa8, 0x2a,
    0x32, 0xe3, 0x6c, 0xd4, 0xd0, 0x12, 0x9a, 0x1e, 0x18, 0x10, 0xce, 0xd3, 0xb0, 0xd8, 0x1e, 0xff,
    0xcc, 0x5b, 0x73, 0x5b, 0xc7, 0x5b, 0xeb, 0x0b, 0xf4, 0x06, 0x04, 0x0e, 0x1c, 0x27, 0xd6, 0x87,
    0x31, 0xde, 0x68, 0x7d, 0xdb, 0xfa, 0x03, 0x32, 0x89, 0x2a, 0x30, 0x5d, 0xb3, 0xf8, 0xc2, 0xeb,
    0xa4  
};

// The 2048-bit modulus for the VeriSign 2006 CA Cert signed with sha1 and an exponent of 65537
static const uint8_t _ProtoSSL_VeriSign2006ServerModulus[] =
{
    0xaf, 0x24, 0x08, 0x08, 0x29, 0x7a, 0x35, 0x9e, 0x60, 0x0c, 0xaa, 0xe7, 0x4b, 0x3b, 0x4e, 0xdc,
    0x7c, 0xbc, 0x3c, 0x45, 0x1c, 0xbb, 0x2b, 0xe0, 0xfe, 0x29, 0x02, 0xf9, 0x57, 0x08, 0xa3, 0x64,
    0x85, 0x15, 0x27, 0xf5, 0xf1, 0xad, 0xc8, 0x31, 0x89, 0x5d, 0x22, 0xe8, 0x2a, 0xaa, 0xa6, 0x42,
    0xb3, 0x8f, 0xf8, 0xb9, 0x55, 0xb7, 0xb1, 0xb7, 0x4b, 0xb3, 0xfe, 0x8f, 0x7e, 0x07, 0x57, 0xec,
    0xef, 0x43, 0xdb, 0x66, 0x62, 0x15, 0x61, 0xcf, 0x60, 0x0d, 0xa4, 0xd8, 0xde, 0xf8, 0xe0, 0xc3,
    0x62, 0x08, 0x3d, 0x54, 0x13, 0xeb, 0x49, 0xca, 0x59, 0x54, 0x85, 0x26, 0xe5, 0x2b, 0x8f, 0x1b,
    0x9f, 0xeb, 0xf5, 0xa1, 0x91, 0xc2, 0x33, 0x49, 0xd8, 0x43, 0x63, 0x6a, 0x52, 0x4b, 0xd2, 0x8f,
    0xe8, 0x70, 0x51, 0x4d, 0xd1, 0x89, 0x69, 0x7b, 0xc7, 0x70, 0xf6, 0xb3, 0xdc, 0x12, 0x74, 0xdb,
    0x7b, 0x5d, 0x4b, 0x56, 0xd3, 0x96, 0xbf, 0x15, 0x77, 0xa1, 0xb0, 0xf4, 0xa2, 0x25, 0xf2, 0xaf,
    0x1c, 0x92, 0x67, 0x18, 0xe5, 0xf4, 0x06, 0x04, 0xef, 0x90, 0xb9, 0xe4, 0x00, 0xe4, 0xdd, 0x3a,
    0xb5, 0x19, 0xff, 0x02, 0xba, 0xf4, 0x3c, 0xee, 0xe0, 0x8b, 0xeb, 0x37, 0x8b, 0xec, 0xf4, 0xd7,
    0xac, 0xf2, 0xf6, 0xf0, 0x3d, 0xaf, 0xdd, 0x75, 0x91, 0x33, 0x19, 0x1d, 0x1c, 0x40, 0xcb, 0x74,
    0x24, 0x19, 0x21, 0x93, 0xd9, 0x14, 0xfe, 0xac, 0x2a, 0x52, 0xc7, 0x8f, 0xd5, 0x04, 0x49, 0xe4,
    0x8d, 0x63, 0x47, 0x88, 0x3c, 0x69, 0x83, 0xcb, 0xfe, 0x47, 0xbd, 0x2b, 0x7e, 0x4f, 0xc5, 0x95,
    0xae, 0x0e, 0x9d, 0xd4, 0xd1, 0x43, 0xc0, 0x67, 0x73, 0xe3, 0x14, 0x08, 0x7e, 0xe5, 0x3f, 0x9f,
    0x73, 0xb8, 0x33, 0x0a, 0xcf, 0x5d, 0x3f, 0x34, 0x87, 0x96, 0x8a, 0xee, 0x53, 0xe8, 0x25, 0x15
};

// The 2048-bit public key modulus for DigiCert Global Root CA
static const uint8_t _ProtoSSL_DigiCertServerModulus[] =
{
    0xE2, 0x3B, 0xE1, 0x11, 0x72, 0xDE, 0xA8, 0xA4, 0xD3, 0xA3, 0x57, 0xAA, 0x50, 0xA2, 0x8F, 0x0B,
    0x77, 0x90, 0xC9, 0xA2, 0xA5, 0xEE, 0x12, 0xCE, 0x96, 0x5B, 0x01, 0x09, 0x20, 0xCC, 0x01, 0x93,
    0xA7, 0x4E, 0x30, 0xB7, 0x53, 0xF7, 0x43, 0xC4, 0x69, 0x00, 0x57, 0x9D, 0xE2, 0x8D, 0x22, 0xDD,
    0x87, 0x06, 0x40, 0x00, 0x81, 0x09, 0xCE, 0xCE, 0x1B, 0x83, 0xBF, 0xDF, 0xCD, 0x3B, 0x71, 0x46,
    0xE2, 0xD6, 0x66, 0xC7, 0x05, 0xB3, 0x76, 0x27, 0x16, 0x8F, 0x7B, 0x9E, 0x1E, 0x95, 0x7D, 0xEE,
    0xB7, 0x48, 0xA3, 0x08, 0xDA, 0xD6, 0xAF, 0x7A, 0x0C, 0x39, 0x06, 0x65, 0x7F, 0x4A, 0x5D, 0x1F,
    0xBC, 0x17, 0xF8, 0xAB, 0xBE, 0xEE, 0x28, 0xD7, 0x74, 0x7F, 0x7A, 0x78, 0x99, 0x59, 0x85, 0x68,
    0x6E, 0x5C, 0x23, 0x32, 0x4B, 0xBF, 0x4E, 0xC0, 0xE8, 0x5A, 0x6D, 0xE3, 0x70, 0xBF, 0x77, 0x10,
    0xBF, 0xFC, 0x01, 0xF6, 0x85, 0xD9, 0xA8, 0x44, 0x10, 0x58, 0x32, 0xA9, 0x75, 0x18, 0xD5, 0xD1,
    0xA2, 0xBE, 0x47, 0xE2, 0x27, 0x6A, 0xF4, 0x9A, 0x33, 0xF8, 0x49, 0x08, 0x60, 0x8B, 0xD4, 0x5F,
    0xB4, 0x3A, 0x84, 0xBF, 0xA1, 0xAA, 0x4A, 0x4C, 0x7D, 0x3E, 0xCF, 0x4F, 0x5F, 0x6C, 0x76, 0x5E,
    0xA0, 0x4B, 0x37, 0x91, 0x9E, 0xDC, 0x22, 0xE6, 0x6D, 0xCE, 0x14, 0x1A, 0x8E, 0x6A, 0xCB, 0xFE,
    0xCD, 0xB3, 0x14, 0x64, 0x17, 0xC7, 0x5B, 0x29, 0x9E, 0x32, 0xBF, 0xF2, 0xEE, 0xFA, 0xD3, 0x0B,
    0x42, 0xD4, 0xAB, 0xB7, 0x41, 0x32, 0xDA, 0x0C, 0xD4, 0xEF, 0xF8, 0x81, 0xD5, 0xBB, 0x8D, 0x58,
    0x3F, 0xB5, 0x1B, 0xE8, 0x49, 0x28, 0xA2, 0x70, 0xDA, 0x31, 0x04, 0xDD, 0xF7, 0xB2, 0x16, 0xF2,
    0x4C, 0x0A, 0x4E, 0x07, 0xA8, 0xED, 0x4A, 0x3D, 0x5E, 0xB5, 0x7F, 0xA3, 0x90, 0xC3, 0xAF, 0x27
};

// only certificates from these authorities are supported
static ProtoSSLCACertT _ProtoSSL_CACerts[] =
{
    // gos2015 CA
    { { "US", "California", "Redwood City", "Electronic Arts, Inc.",
        "Global Online Studio/emailAddress=GOSDirtysockSupport@ea.com",
        "GOS 2015 Certificate Authority" },
      SSL_CACERTFLAG_GOSCA|SSL_CACERTFLAG_CAPROVIDER, ASN_OBJ_RSA_PKCS_SHA256, 0,
      sizeof(_ProtoSSL_GOS2015ServerModulus2048), _ProtoSSL_GOS2015ServerModulus2048,
      3, { 0x01, 0x00, 0x01 },
      0, NULL, NULL, &_ProtoSSL_CACerts[1] },
    // gos2019 CA
    { { "US", "California", "Redwood City", "Electronic Arts, Inc.",
        "EADP Gameplay Services",
        "Gameplay Services 2019 Certificate Authority" },
      SSL_CACERTFLAG_GOSCA, ASN_OBJ_ECDSA_KEY, ASN_OBJ_ECDSA_SECP256R1,
      sizeof(_ProtoSSL_GS2019ServerKey), _ProtoSSL_GS2019ServerKey,
      0, { 0x00 },
      0, NULL, NULL, &_ProtoSSL_CACerts[2] },
    // verisign 2006 CA
    { { "US", "", "", "VeriSign, Inc.",
        "VeriSign Trust Network, (c) 2006 VeriSign, Inc. - For authorized use only",
        "VeriSign Class 3 Public Primary Certification Authority - G5" },
      SSL_CACERTFLAG_NONE, ASN_OBJ_RSA_PKCS_SHA1, 0,
      256, _ProtoSSL_VeriSign2006ServerModulus,
      3, { 0x01, 0x00, 0x01 },
      0, NULL, NULL, &_ProtoSSL_CACerts[3]},
    // digicert CA
    { { "US", "", "", "DigiCert Inc", "www.digicert.com", "DigiCert Global Root CA" },
      SSL_CACERTFLAG_NONE, ASN_OBJ_RSA_PKCS_SHA256, 0,
      256, _ProtoSSL_DigiCertServerModulus,
      3, { 0x01, 0x00, 0x01 },
      0, NULL, NULL, NULL },
};


/*** Private functions ************************************************************/

/*
    safe (bounded) reading
*/

/*F********************************************************************************/
/*!
    \Function _SafeRead8

    \Description
        Read a uint8_t from buffer 

    \Input *pData           - pointer to buffer to read from
    \Input *pDataEnd        - pointer to end of buffer

    \Output
        uint8_t             - value from buffer, or zero if buffer overrun

    \Version 01/02/2018 (jbrookes)
*/
/********************************************************************************F*/
static uint8_t _SafeRead8(const uint8_t *pData, const uint8_t *pDataEnd)
{
    uint8_t u8 = ((pData+1) <= pDataEnd) ? pData[0] : 0;
    return(u8);
}

/*F********************************************************************************/
/*!
    \Function _SafeRead16

    \Description
        Read a uint16_t from buffer in network order

    \Input *pData           - pointer to buffer to read from
    \Input *pDataEnd        - pointer to end of buffer

    \Output
        uint16_t            - value from buffer, or zero if buffer overrun

    \Version 01/02/2018 (jbrookes)
*/
/********************************************************************************F*/
static uint16_t _SafeRead16(const uint8_t *pData, const uint8_t *pDataEnd)
{
    uint16_t u16 = ((pData+2) <= pDataEnd) ? ((uint16_t)pData[0]<<8)|((uint16_t)pData[1]) : 0;
    return(u16);
}

/*F********************************************************************************/
/*!
    \Function _SafeRead24

    \Description
        Read a 24bit value from buffer in network order

    \Input *pData           - pointer to buffer to read from
    \Input *pDataEnd        - pointer to end of buffer

    \Output
        uint32_t            - value from buffer, or zero if buffer overrun

    \Version 01/02/2018 (jbrookes)
*/
/********************************************************************************F*/
static uint32_t _SafeRead24(const uint8_t *pData, const uint8_t *pDataEnd)
{
    uint32_t u32 = ((pData+3) <= pDataEnd) ? ((uint32_t)pData[0]<<16)|((uint32_t)pData[1]<<8)|((uint32_t)pData[2]) : 0;
    return(u32);
}

/*F********************************************************************************/
/*!
    \Function _SafeRead32

    \Description
        Read a uint32_t from buffer in network order

    \Input *pData           - pointer to buffer to read from
    \Input *pDataEnd        - pointer to end of buffer

    \Output
        uint32_t            - value from buffer, or zero if buffer overrun

    \Version 01/02/2018 (jbrookes)
*/
/********************************************************************************F*/
static uint32_t _SafeRead32(const uint8_t *pData, const uint8_t *pDataEnd)
{
    uint32_t u32 = ((pData+4) <= pDataEnd) ? ((uint32_t)pData[0]<<24)|((uint32_t)pData[1]<<16)|((uint32_t)pData[2]<<8)|((uint32_t)pData[3]) : 0;
    return(u32);
}

/*F********************************************************************************/
/*!
    \Function _SafeReadBytes

    \Description
        Read specified bytes from buffer

    \Input *pBuffer         - [out] buffer to write to
    \Input uBufSize         - size of output buffe
    \Input *pData           - pointer to buffer to read from
    \Input uNumBytes        - number of bytes to read
    \Input *pDataEnd        - pointer to end of buffer

    \Version 01/02/2018 (jbrookes)
*/
/********************************************************************************F*/
static void _SafeReadBytes(uint8_t *pBuffer, uint32_t uBufSize, const uint8_t *pData, uint32_t uNumBytes, const uint8_t *pDataEnd)
{
    if ((pData+uNumBytes) <= pDataEnd)
    {
        ds_memcpy_s(pBuffer, uBufSize, pData, uNumBytes);
    }
    else
    {
        ds_memclr(pBuffer, uBufSize);
    }
}

/*F********************************************************************************/
/*!
    \Function _SafeReadString

    \Description
        Read length-delimited string from buffer

    \Input *pBuffer         - [out] string buffer to write to
    \Input uBufSize         - size of output buffe
    \Input *pString         - pointer to source to read from
    \Input uStrLen          - length of source string
    \Input *pDataEnd        - pointer to end of buffer

    \Version 01/02/2018 (jbrookes)
*/
/********************************************************************************F*/
static void _SafeReadString(char *pBuffer, uint32_t uBufSize, const char *pString, uint32_t uStrLen, const uint8_t *pDataEnd)
{
    if ((pString+uStrLen) <= (const char *)pDataEnd)
    {
        ds_strsubzcpy(pBuffer, uBufSize, pString, uStrLen);
    }
    else
    {
        ds_memclr(pBuffer, uBufSize);
    }
}

/*
    asn.1 parsing routines
*/

/*F********************************************************************************/
/*!
    \Function _AsnGetHashType

    \Description
        Get CryptHashE from ASN.1 hash object type

    \Input iHashType    - asn.1 hash type

    \Output
        CryptHashTypeE  - hash type

    \Version 11/28/2017 (jbrookes)
*/
/********************************************************************************F*/
static CryptHashTypeE _AsnGetHashType(int32_t iHashType)
{
    CryptHashTypeE eHashType;
    // convert from asn object type to crypt hash type
    switch (iHashType)
    {
        case ASN_OBJ_MD5:
            eHashType = CRYPTHASH_MD5;
            break;
        case ASN_OBJ_SHA1:
            eHashType = CRYPTHASH_SHA1;
            break;
        case ASN_OBJ_SHA256:
            eHashType = CRYPTHASH_SHA256;
            break;
        case ASN_OBJ_SHA384:
            eHashType = CRYPTHASH_SHA384;
            break;
        case ASN_OBJ_SHA512:
            eHashType = CRYPTHASH_SHA512;
            break;
        default:
            eHashType = CRYPTHASH_NULL;
            break;
    }
    // return hash type to caller
    return(eHashType);
}

/*F********************************************************************************/
/*!
    \Function _AsnGetObject

    \Description
        Return OID based on type

    \Input iType    - Type of OID (ASN_OBJ_*)

    \Output
        ASNObjectT *- pointer to OID, or NULL if not found

    \Version 03/18/2015 (jbrookes)
*/
/********************************************************************************F*/
static const ASNObjectT *_AsnGetObject(int32_t iType)
{
    const ASNObjectT *pObject;
    int32_t iIndex;

    // locate the matching type
    for (iIndex = 0, pObject = NULL; _SSL_ObjectList[iIndex].iType != ASN_OBJ_NONE; iIndex += 1)
    {
        if (_SSL_ObjectList[iIndex].iType == iType)
        {
            pObject = &_SSL_ObjectList[iIndex];
            break;
        }
    }

    return(pObject);
}

/*F********************************************************************************/
/*!
    \Function _AsnParseHeader

    \Description
        Parse an asn.1 header

    \Input *pData    - pointer to header data to parse
    \Input *pLast    - pointer to end of header
    \Input *pType    - pointer to storage for header type
    \Input *pSize    - pointer to storage for data size

    \Output uint8_t * - pointer to next block, or NULL if end of stream

    \Version 03/08/2002 (gschaefer)
*/
/********************************************************************************F*/
static const uint8_t *_AsnParseHeader(const uint8_t *pData, const uint8_t *pLast, int32_t *pType, int32_t *pSize)
{
    int32_t iCnt;
    uint32_t uLen;
    int32_t iType, iSize;

    // reset the output
    if (pSize != NULL)
    {
        *pSize = 0;
    }
    if (pType != NULL)
    {
        *pType = 0;
    }

    /* handle end of data (including early termination due to data truncation or
       invalid data) and make sure we have at least enough data for the type and
       size  */
    if ((pData == NULL) || (pData+2 > pLast))
    {
        return(NULL);
    }

    // get the type
    iType = *pData++;
    if (pType != NULL)
    {
        *pType = iType;
    }

    // figure the length
    if ((uLen = *pData++) > 127)
    {
        // get number of bytes
        iCnt = (uLen & 127);
        // validate length (do not allow overflow) and make sure there is enough data
        if ((iCnt > (int32_t)sizeof(uLen)) || ((pData + iCnt) > pLast))
        {
            return(NULL);
        }
        // calc the length
        for (uLen = 0; iCnt > 0; --iCnt)
        {
            uLen = (uLen << 8) | *pData++;
        }
    }
    iSize = (signed)uLen;
    // validate record size
    if ((iSize < 0) || ((pData+iSize) > pLast))
    {
        return(NULL);
    }
    // save the size
    if (pSize != NULL)
    {
        *pSize = iSize;
    }

    #if DEBUG_ALL_OBJS
    NetPrintf(("protossl: _AsnParseHeader type=%s (0x%02x) size=%d\n",
        _SSL3_strAsnTypes[iType&~(ASN_CONSTRUCT|ASN_IMPLICIT_TAG|ASN_EXPLICIT_TAG)],
        iType, iSize));
    #if DEBUG_RAW_DATA
    NetPrintMem(pData, iSize, _SSL3_strAsnTypes[iType&~(ASN_CONSTRUCT|ASN_IMPLICIT_TAG|ASN_EXPLICIT_TAG)]);
    #endif
    #endif

    // return pointer to next
    return(pData);
}

/*F********************************************************************************/
/*!
    \Function _AsnParseHeaderType

    \Description
        Parse an asn.1 header of specified type

    \Input *pData   - pointer to header data to parse
    \Input *pLast   - pointer to end of header
    \Input iType    - type of header to extract
    \Input *pSize   - pointer to storage for data size

    \Output uint8_t * - pointer to next block, or NULL if end of stream or unexpected type

    \Version 10/10/2012 (jbrookes)
*/
/********************************************************************************F*/
static const uint8_t *_AsnParseHeaderType(const uint8_t *pData, const uint8_t *pLast, int32_t iType, int32_t *pSize)
{
    int32_t iTypeParsed;
    pData = _AsnParseHeader(pData, pLast, &iTypeParsed, pSize);
    if (iTypeParsed != iType)
    {
        return(NULL);
    }
    return(pData);
}

/*F********************************************************************************/
/*!
    \Function _AsnParseObject

    \Description
        Parse an object type

    \Input *pData   - pointer to object
    \Input iSize    - size of object

    \Output
        int32_t     - type of object; zero if object is unrecognized

    \Version 03/08/2002 (gschaefer)
*/
/********************************************************************************F*/
static int32_t _AsnParseObject(const uint8_t *pData, int32_t iSize)
{
    int32_t iType = 0;
    int32_t iIndex;

    // locate the matching type
    for (iIndex = 0; _SSL_ObjectList[iIndex].iType != ASN_OBJ_NONE; ++iIndex)
    {
        if ((iSize >= _SSL_ObjectList[iIndex].iSize) && (memcmp(pData, _SSL_ObjectList[iIndex].strData, _SSL_ObjectList[iIndex].iSize) == 0))
        {
            // save the type and return it
            iType = _SSL_ObjectList[iIndex].iType;

            #if DEBUG_ALL_OBJS
            NetPrintf(("protossl: _AsnParseObject obj=%s (%d)\n", _SSL3_strAsnObjs[iType], iType));
            #if DEBUG_RAW_DATA
            NetPrintMem(pData, _SSL_ObjectList[iIndex].iSize, _SSL3_strAsnObjs[iType]);
            #endif
            #endif

            break;
        }
    }

    #if DEBUG_ALL_OBJS
    if (iType == 0)
    {
        NetPrintMem(pData, iSize, "unrecognized asn.1 object");
    }
    #endif

    return(iType);
}

/*F********************************************************************************/
/*!
    \Function _AsnParseString

    \Description
        Extract a string

    \Input *pData   - pointer to data to extract string from
    \Input iSize    - size of source data
    \Input *pString - pointer to buffer to copy string into
    \Input iLength  - size of buffer
    \Input iType    - ASN string type

    \Notes
        The number of characters copied will be whichever is smaller between
        iSize and iLength-1.  If iLength-1 is greater than iSize (ie, the buffer
        is larger than the source string) the string output in pString will be
        NULL-terminated.

    \Version 03/08/2002 (gschaefer)
*/
/********************************************************************************F*/
static void _AsnParseString(const uint8_t *pData, int32_t iSize, char *pString, int32_t iLength, int32_t iType)
{
    if (iType != ASN_TYPE_UNICODESTR)
    {
        for (; (iSize > 0) && (iLength > 1); --iSize, --iLength)
        {
            *pString++ = *pData++;
        }
        if (iLength > 0)
        {
            *pString = 0;
        }
    }
    else // we do a straight conversion to ASCII here
    {
        for (pData += 1; (iSize > 0) && (iLength > 1); iSize -= 2, iLength -= 1)
        {
            *pString++ = *pData;
            pData += 2;
        }
        if (iLength > 0)
        {
            *pString = 0;
        }
    }
}

/*F********************************************************************************/
/*!
    \Function _AsnParseStringMulti

    \Description
        Extract a string, appending to output instead of overwriting.

    \Input *pData   - pointer to data to extract string from
    \Input iSize    - size of source data
    \Input *pString - pointer to buffer to copy string into
    \Input iLength  - size of buffer

    \Notes
        The number of characters copied will be whichever is smaller between
        iSize and iLength-1.  If iLength-1 is greater than iSize (ie, the buffer
        is larger than the source string) the string output in pString will be
        NULL-terminated.

    \Version 03/25/2009 (jbrookes)
*/
/********************************************************************************F*/
static void _AsnParseStringMulti(const uint8_t *pData, int32_t iSize, char *pString, int32_t iLength)
{
    // find append point
    for (; (*pString != '\0') && (iLength > 1); --iLength)
    {
        pString += 1;
    }
    // extract
    for (; (iSize > 0) && (iLength > 1); --iSize, --iLength)
    {
        *pString++ = *pData++;
    }
    // terminate
    if (iLength > 0)
    {
        *pString = '\0';
    }
}

/*F********************************************************************************/
/*!
    \Function _AsnParseIdent

    \Description
        Extract an Ident (subject/issuer) from certificate

    \Input *pData   - pointer to data to extract string from
    \Input iSize    - size of source data
    \Input *pIdent  - [out] storage for parsed ident fields

    \Output
        const uint8_t *     - pointer past end of ident

    \Version 10/09/2012 (jbrookes)
*/
/********************************************************************************F*/
static const uint8_t *_AsnParseIdent(const uint8_t *pData, int32_t iSize, ProtoSSLCertIdentT *pIdent)
{
    int32_t iObjType, iType;
    const uint8_t *pDataEnd;

    for (iObjType = 0, pDataEnd = pData+iSize; (pData = _AsnParseHeader(pData, pDataEnd, &iType, &iSize)) != NULL; )
    {
        // skip past seqn/set references
        if ((iType == ASN_TYPE_SEQN+ASN_CONSTRUCT) || (iType == ASN_TYPE_SET+ASN_CONSTRUCT))
        {
            continue;
        }
        if (iType == ASN_TYPE_OBJECT+ASN_PRIMITIVE)
        {
            iObjType = _AsnParseObject(pData, iSize);
        }
        if ((iType == ASN_TYPE_PRINTSTR+ASN_PRIMITIVE) || (iType == ASN_TYPE_UTF8STR+ASN_PRIMITIVE) || (iType == ASN_TYPE_T61+ASN_PRIMITIVE) || (iType == ASN_TYPE_UNICODESTR+ASN_PRIMITIVE))
        {
            if (iObjType == ASN_OBJ_COUNTRY)
            {
                _AsnParseString(pData, iSize, pIdent->strCountry, sizeof(pIdent->strCountry), iType);
            }
            if (iObjType == ASN_OBJ_STATE)
            {
                _AsnParseString(pData, iSize, pIdent->strState, sizeof(pIdent->strState), iType);
            }
            if (iObjType == ASN_OBJ_CITY)
            {
                _AsnParseString(pData, iSize, pIdent->strCity, sizeof(pIdent->strCity), iType);
            }
            if (iObjType == ASN_OBJ_ORGANIZATION)
            {
                _AsnParseString(pData, iSize, pIdent->strOrg, sizeof(pIdent->strOrg), iType);
            }
            if (iObjType == ASN_OBJ_UNIT)
            {
                if (pIdent->strUnit[0] != '\0')
                {
                    ds_strnzcat(pIdent->strUnit, ", ", sizeof(pIdent->strUnit));
                }
                _AsnParseStringMulti(pData, iSize, pIdent->strUnit, sizeof(pIdent->strUnit));
            }
            if (iObjType == ASN_OBJ_COMMON)
            {
                _AsnParseString(pData, iSize, pIdent->strCommon, sizeof(pIdent->strCommon), iType);
            }
            iObjType = 0;
        }
        pData += iSize;
    }

    return(pDataEnd);
}

/*F********************************************************************************/
/*!
    \Function _AsnParseDate

    \Description
        Parse and extract a date object from ASN.1 certificate

    \Input *pData   - pointer to header data
    \Input iSize    - size of object
    \Input *pBuffer - [out] output for date string object
    \Input iBufSize - size of output buffer
    \Input *pDateTime - [out] storage for converted time, optional

    \Output
        const uint8_t * - pointer past object, or NULL if an error occurred

    \Version 10/10/2012 (jbrookes)
*/
/********************************************************************************F*/
static const uint8_t *_AsnParseDate(const uint8_t *pData, int32_t iSize, char *pBuffer, int32_t iBufSize, uint64_t *pDateTime)
{
    int32_t iType;
    if (((pData = _AsnParseHeader(pData, pData+iSize, &iType, &iSize)) == NULL) || ((iType != ASN_TYPE_UTCTIME) && (iType != ASN_TYPE_GENERALIZEDTIME)))
    {
        return(NULL);
    }
    _AsnParseString(pData, iSize, pBuffer, iBufSize, iType);
    if (pDateTime != NULL)
    {
        *pDateTime = ds_strtotime2(pBuffer, iType == ASN_TYPE_UTCTIME ? TIMETOSTRING_CONVERSION_ASN1_UTCTIME : TIMETOSTRING_CONVERSION_ASN1_GENTIME);
    }
    pData += iSize;
    return(pData);
}

/*F********************************************************************************/
/*!
    \Function _AsnParseBinaryPtr

    \Description
        Parse and extract a binary object from ASN.1 certificate

    \Input *pData   - pointer to header data
    \Input *pLast   - pointer to end of data
    \Input iType    - type of data we are expecting
    \Input **ppObj   - [out] storage for pointer to binary object
    \Input *pObjSize - [out] storage for size of binary object
    \Input *pName   - name of object (used for debug output)

    \Output
        const uint8_t * - pointer past object, or NULL if an error occurred

    \Version 11/18/2013 (jbrookes)
*/
/********************************************************************************F*/
static const uint8_t *_AsnParseBinaryPtr(const uint8_t *pData, const uint8_t *pLast, int32_t iType, uint8_t **ppObj, int32_t *pObjSize, const char *pName)
{
    int32_t iSize;
    if ((pData = _AsnParseHeaderType(pData, pLast, iType, &iSize)) == NULL)
    {
        NetPrintf(("protossl: _AsnParseBinary: could not get %s\n", pName));
        return(NULL);
    }
    // skip leading zero if present
    if ((iSize > 0) && (*pData == '\0'))
    {
        pData += 1;
        iSize -= 1;
    }
    // save object pointer
    *ppObj = (uint8_t *)pData;
    // save object size
    *pObjSize = iSize;
    // skip object
    return(pData+iSize);
}

/*F********************************************************************************/
/*!
    \Function _AsnParseBinary

    \Description
        Parse and extract a binary object from ASN.1 certificate

    \Input *pData   - pointer to header data
    \Input *pLast   - pointer to end of data
    \Input iType    - type of data we are expecting
    \Input *pBuffer - [out] output for binary object (may be null to skip)
    \Input iBufSize - size of output buffer
    \Input *pOutSize - [out] output for binary object size
    \Input *pName   - name of object (used for debug output)

    \Output
        const uint8_t * - pointer past object, or NULL if an error occurred

    \Version 10/10/2012 (jbrookes)
*/
/********************************************************************************F*/
static const uint8_t *_AsnParseBinary(const uint8_t *pData, const uint8_t *pLast, int32_t iType, uint8_t *pBuffer, int32_t iBufSize, int32_t *pOutSize, const char *pName)
{
    const uint8_t *pObjData = NULL, *pNext;
    // parse object info
    pNext = _AsnParseBinaryPtr(pData, pLast, iType, (uint8_t **)&pObjData, pOutSize, pName);
    // save data
    if ((pObjData != NULL) && (pBuffer != NULL))
    {
        // validate size
        if (*pOutSize > iBufSize)
        {
            NetPrintf(("protossl: _AsnParseBinary: %s is too large (size=%d, max=%d)\n", pName, *pOutSize, iBufSize));
            return(NULL);
        }
        ds_memcpy(pBuffer, pObjData, *pOutSize);
    }
    // skip object
    return(pNext);
}

/*F********************************************************************************/
/*!
    \Function _AsnParseOptional

    \Description
        Parse optional object of ASN.1 certificate

    \Input *pData   - pointer to header data
    \Input iSize    - size of header
    \Input *pCert   - pointer to certificate to fill in from header data

    \Output
        int32_t     - negative=error, zero=no error

    \Version 10/10/2012 (jbrookes) Extracted from _AsnParseCertificate()
*/
/********************************************************************************F*/
static int32_t _AsnParseOptional(const uint8_t *pData, int32_t iSize, X509CertificateT *pCert)
{
    const uint8_t *pLast;
    int32_t iCritical, iObjType, iLen, iType;

    for (iCritical = iObjType = 0, pLast = pData+iSize; (pData = _AsnParseHeader(pData, pLast, &iType, &iSize)) != NULL; )
    {
        // ignore seqn/set references
        if ((iType == ASN_TYPE_SEQN+ASN_CONSTRUCT) || (iType == ASN_TYPE_SET+ASN_CONSTRUCT))
        {
            continue;
        }

        // parse the object type
        if (iType == ASN_TYPE_OBJECT+ASN_PRIMITIVE)
        {
            iObjType = _AsnParseObject(pData, iSize);
        }

        // parse a subject alternative name (SAN) object
        if (iObjType == ASN_OBJ_SUBJECT_ALT)
        {
            if (iType == ASN_TYPE_OCTSTRING+ASN_PRIMITIVE)
            {
                // save reference to subject alternative blob
                pCert->iSubjectAltLen = iSize;
                pCert->pSubjectAlt = pData;
            }
        }

        // parse a basic constraints object
        if (iObjType == ASN_OBJ_BASIC_CONSTRAINTS)
        {
            // obj_basic_constraints: [boolean: critical]<oct><seq><boolean: isCA>[integer: pathLenConstraints]
            if (iType == ASN_TYPE_OCTSTRING+ASN_PRIMITIVE)
            {
                // if no critical flag is present, mark it as critical. ex: https://www.wellsfargo.com/
                if (iCritical == 0)
                {
                    iCritical = 1;
                }
                // do not add pData for oct
                continue;
            }

            if ((iType == ASN_TYPE_BOOLEAN+ASN_PRIMITIVE) && (iSize == 1))
            {
                if (!iCritical)
                {
                    // check if the critical flag is set (the basic constraints MUST be critical)
                    if ((iCritical = *pData) == 0)
                    {
                        return(-1);
                    }
                }
                else
                {
                    // this is the flag to indicate whether it's a CA
                    pCert->iCertIsCA = (*pData != 0) ? TRUE : FALSE;
                }
            }

            if ((iType == ASN_TYPE_INTEGER+ASN_PRIMITIVE) && (pCert->iCertIsCA != FALSE) && (iSize <= (signed)sizeof(pCert->iMaxHeight)))
            {
                for (iLen = 0; iLen < iSize; iLen++)
                {
                    pCert->iMaxHeight = (pCert->iMaxHeight << 8) | pData[iLen];
                }
                /* As per http://tools.ietf.org/html/rfc2459#section-4.2.1.10: A value of zero indicates that only an end-entity certificate
                   may follow in the path. Where it appears, the pathLenConstraint field MUST be greater than or equal to zero. Where pathLenConstraint
                   does not appear, there is no limit to the allowed length of the certification path. In our case (iMaxHeight), a value of zero means
                   no pathLenConstraint is present, 1 means the pathLenConstraint is 0, 2 means the pathLenConstraint is 1, ... */
                //$$ todo - https://tools.ietf.org/html/rfc5280#section-4.2.1.9 updates this to add the keyCertSign bit in the key usage extension as a requirement
                if (pCert->iMaxHeight++ < 0)
                {
                    return(-2);
                }
            }
        }
        pData += iSize;
    }
    if (pCert->pSubjectAlt != NULL)
    {
        NetPrintfVerbose((DEBUG_VAL_CERT, 0, "protossl: parsed SAN; length=%d\n", pCert->iSubjectAltLen));
    }
    return(0);
}

/*F********************************************************************************/
/*!
    \Function _AsnParseSequencedObject

    \Description
        Parse a sequenced object

    \Input *pData   - pointer to object to parse
    \Input *pLast   - pointer to end of data
    \Input *pObjType - [out] storage for parsed object type

    \Output
        uint8_t *    - pointer past end of object, or NULL

    \Version 11/21/2017 (jbrookes) 
*/
/********************************************************************************F*/
static const uint8_t *_AsnParseSequencedObject(const uint8_t *pData, const uint8_t *pLast, int32_t *pObjType)
{
    int32_t iSize;
    if ((pData = _AsnParseHeaderType(pData, pLast, ASN_TYPE_SEQN+ASN_CONSTRUCT, &iSize)) == NULL)
    {
        return(NULL);
    }
    if ((pData = _AsnParseHeaderType(pData, pLast, ASN_TYPE_OBJECT+ASN_PRIMITIVE, &iSize)) == NULL)
    {
        return(NULL);
    }
    if ((*pObjType = _AsnParseObject(pData, iSize)) < 0)
    {
        return(NULL);
    }
    return(pData+iSize);
}

/*F********************************************************************************/
/*!
    \Function _AsnParseSignatureAlgorithms

    \Description
        Parse Signature Algorithms object of ASN.1 certificate

    \Input *pData   - pointer to header data
    \Input *pLast   - pointer to end of data
    \Input *pCert   - pointer to certificate to fill in from sigalg object

    \Output
        uint8_t *   - pointer past end of object

    \Version 11/21/2017 (jbrookes) 
*/
/********************************************************************************F*/
static const uint8_t *_AsnParseSignatureAlgorithms(const uint8_t *pData, const uint8_t *pLast, X509CertificateT *pCert)
{
    int32_t iMgfFunc, iSize;
    const uint8_t *pData2;

    // parse signature algorithm identifier
    if ((pData = _AsnParseHeaderType(pData, pLast, ASN_TYPE_OBJECT+ASN_PRIMITIVE, &iSize)) == NULL)
    {
        NetPrintf(("protossl: _AsnParseCertificate: could not get signature algorithm identifier\n"));
        return(NULL);
    }
    pCert->iSigType = _AsnParseObject(pData, iSize);
    pData += iSize;

    // RSA-PKCS?
    if ((pCert->iSigType >= ASN_OBJ_RSA_PKCS_MD5) && (pCert->iSigType <= ASN_OBJ_RSA_PKCS_SHA512))
    {
        // get sig hash type from sig type
        pCert->iSigHash = pCert->iSigType-ASN_OBJ_RSA_PKCS_MD5+ASN_OBJ_MD5;
        return(pData);
    }
    else if (pCert->iSigType == ASN_OBJ_ECDSA_SHA256)
    {
        pCert->iSigHash = ASN_OBJ_SHA256;
        return(pData);
    }
    else if (pCert->iSigType == ASN_OBJ_ECDSA_SHA384)
    {
        pCert->iSigHash = ASN_OBJ_SHA384;
        return(pData);
    }
    else if (pCert->iSigType != ASN_OBJ_RSASSA_PSS)
    {
        NetPrintf(("protossl: unsupported signature algorithm %d\n", pCert->iSigType));
        return(NULL);
    }

    // set default parameter types, which are used when only partial settings are specified
    pCert->iSigHash = ASN_OBJ_SHA1;
    pCert->iSigSalt = 20;
    iMgfFunc = ASN_OBJ_PKCS1_MGF1;
    pCert->iMgfHash = ASN_OBJ_SHA1;

    // parse rsassa-pss parameter sequence; see https://tools.ietf.org/html/rfc3447#appendix-A.2.3
    if ((pData = _AsnParseHeaderType(pData, pLast, ASN_TYPE_SEQN+ASN_CONSTRUCT, &iSize)) == NULL)
    {
        NetPrintf(("protossl: _AsnParseCertificate: could not parse rsassa_pss parameter sequence\n"));
        return(NULL);
    }

    // parse optional signature hash object
    if ((pData2 = _AsnParseHeaderType(pData, pLast, ASN_EXPLICIT_TAG+ASN_PRIMITIVE+0, &iSize)) != NULL)
    {
        if ((pData = _AsnParseSequencedObject(pData2, pLast, &pCert->iSigHash)) == NULL)
        {
            NetPrintf(("protossl: _AsnParseSignatureAlgorithm: error parsing rsassa_pss hash parameter\n"));
            return(NULL);
        }
    }

    // parse optional mgf+hash object
    if ((pData2 = _AsnParseHeaderType(pData, pLast, ASN_EXPLICIT_TAG+ASN_PRIMITIVE+1, &iSize)) != NULL)
    {
        // parse mgf
        if (((pData2 = _AsnParseSequencedObject(pData2, pLast, &iMgfFunc)) == NULL) || (iMgfFunc != ASN_OBJ_PKCS1_MGF1))
        {
            NetPrintf(("protossl: _AsnParseSignatureAlgorithm: error parsing rsassa_pss mgf parameter"));
            return(NULL);
        }
        // parse mgf hash
        if ((pData = _AsnParseSequencedObject(pData2, pLast, &pCert->iMgfHash)) == NULL)
        {
            NetPrintf(("protossl: _AsnParseSignatureAlgorithm: error parsing rsassa_pss hash parameter\n"));
            return(NULL);
        }
    }

    // look for explicit signature parameters (optional)
    if ((pData2 = _AsnParseHeaderType(pData, pLast, ASN_EXPLICIT_TAG+ASN_PRIMITIVE+2, &iSize)) != NULL)
    {
        int32_t iSigSaltLen = 0;
        uint8_t uSigSalt = 0;

        // parse salt length
        if (((pData = _AsnParseBinary(pData2, pLast, ASN_TYPE_INTEGER+ASN_PRIMITIVE, &uSigSalt, sizeof(uSigSalt), &iSigSaltLen, "salt length")) == NULL) || (iSigSaltLen != 1))
        {
            return(NULL);
        }
        pCert->iSigSalt = uSigSalt;
    }

    #if DEBUG_VAL_CERT
    NetPrintf(("protossl: rsassa-pss parameters\n"));
    NetPrintf(("protossl: iSigType=%d\n", pCert->iSigType));
    NetPrintf(("protossl: iSigHash=%d\n", pCert->iSigHash));
    NetPrintf(("protossl: iMgfFunc=%d\n", iMgfFunc));
    NetPrintf(("protossl: iMgfHash=%d\n", pCert->iMgfHash));
    NetPrintf(("protossl: iSigSalt=%d\n", pCert->iSigSalt));
    #endif

    // return pointer past end of data
    return(pData);
}

/*F********************************************************************************/
/*!
    \Function _AsnParseCertificate

    \Description
        Parse an x.509 certificate in ASN.1 format

    \Input *pCert   - pointer to certificate to fill in from header data
    \Input *pData   - pointer to header data
    \Input iSize    - size of header

    \Output
        int32_t     - negative=error, zero=no error

    \Version 03/08/2002 (gschaefer)
*/
/********************************************************************************F*/
static int32_t _AsnParseCertificate(X509CertificateT *pCert, const uint8_t *pData, int32_t iSize)
{
    const uint8_t *pInfData;
    const uint8_t *pInfSkip;
    const uint8_t *pSigSkip;
    const uint8_t *pKeySkip;
    const uint8_t *pKeyData;
    const uint8_t *pLast = pData+iSize;
    const CryptHashT *pHash;
    int32_t iKeySize, iType;

    // clear the certificate
    ds_memclr(pCert, sizeof(*pCert));

    // process the base sequence
    if ((pData = _AsnParseHeaderType(pData, pLast, ASN_TYPE_SEQN+ASN_CONSTRUCT, &iSize)) == NULL)
    {
        NetPrintf(("protossl: _AsnParseCertificate: could not parse base sequence\n"));
        return(-1);
    }

    // process the info sequence
    if ((pData = _AsnParseHeaderType(pInfData = pData, pLast, ASN_TYPE_SEQN+ASN_CONSTRUCT, &iSize)) == NULL)
    {
        NetPrintf(("protossl: _AsnParseCertificate: could not parse info sequence\n"));
        return(-2);
    }
    pInfSkip = pData+iSize;

    // skip any non-integer tag (optional version)
    if (*pData != ASN_TYPE_INTEGER+ASN_PRIMITIVE)
    {
        if ((pData = _AsnParseHeader(pData, pLast, NULL, &iSize)) == NULL)
        {
            NetPrintf(("protossl: _AsnParseCertificate: could not skip non-integer tag\n"));
            return(-3);
        }
        pData += iSize;
    }

    // grab the certificate serial number
    if (((pData = _AsnParseHeader(pData, pInfSkip, &iType, &iSize)) == NULL) || ((unsigned)iSize > sizeof(pCert->SerialData)))
    {
        NetPrintf(("protossl: _AsnParseCertificate: could not get certificate serial number (iSize=%d)\n", iSize));
        return(-4);
    }
    pCert->iSerialSize = iSize;
    ds_memcpy(pCert->SerialData, pData, iSize);
    pData += iSize;

    // find signature algorithm
    if ((pData = _AsnParseHeaderType(pData, pInfSkip, ASN_TYPE_SEQN+ASN_CONSTRUCT, &iSize)) == NULL)
    {
        NetPrintf(("protossl: _AsnParseCertificate: could not get signature algorithm\n"));
        return(-5);
    }
    pSigSkip = pData+iSize;

    // get the signature algorithm type
    if ((pData = _AsnParseHeaderType(pData, pInfSkip, ASN_TYPE_OBJECT+ASN_PRIMITIVE, &iSize)) == NULL)
    {
        NetPrintf(("protossl: _AsnParseCertificate: could not get signature algorithm type\n"));
        return(-6);
    }
    if ((pCert->iSigType = _AsnParseObject(pData, iSize)) == ASN_OBJ_NONE)
    {
        NetPrintMem(pData, iSize, "protossl: unsupported signature algorithm");
        return(-7);
    }

    // parse issuer
    if ((pData = _AsnParseHeaderType(pSigSkip, pInfSkip, ASN_TYPE_SEQN+ASN_CONSTRUCT, &iSize)) == NULL)
    {
        NetPrintf(("protossl: _AsnParseCertificate: could not get issuer\n"));
        return(-8);
    }
    pData = _AsnParseIdent(pData, iSize, &pCert->Issuer);

    // parse the validity info
    if ((pData = _AsnParseHeaderType(pData, pInfSkip, ASN_TYPE_SEQN+ASN_CONSTRUCT, &iSize)) == NULL)
    {
        NetPrintf(("protossl: _AsnParseCertificate: could not get validity info\n"));
        return(-9);
    }

    // get validity dates
    if ((pData = _AsnParseDate(pData, iSize, pCert->strGoodFrom, sizeof(pCert->strGoodFrom), &pCert->uGoodFrom)) == NULL)
    {
        NetPrintf(("protossl: _AsnParseCertificate: could not get from date\n"));
        return(-10);
    }
    if ((pData = _AsnParseDate(pData, iSize, pCert->strGoodTill, sizeof(pCert->strGoodTill), &pCert->uGoodTill)) == NULL)
    {
        NetPrintf(("protossl: _AsnParseCertificate: could not get to date\n"));
        return(-11);
    }
    #if DEBUG_VAL_CERT
    {
        char strTimeFrom[32], strTimeTill[32];
        NetPrintf(("protossl: certificate valid from %s to %s\n",
            ds_secstostr(pCert->uGoodFrom, TIMETOSTRING_CONVERSION_RFC_0822, FALSE, strTimeFrom, sizeof(strTimeFrom)),
            ds_secstostr(pCert->uGoodTill, TIMETOSTRING_CONVERSION_RFC_0822, FALSE, strTimeTill, sizeof(strTimeTill))));
    }
    #endif

    // get subject
    if ((pData = _AsnParseHeaderType(pData, pInfSkip, ASN_TYPE_SEQN+ASN_CONSTRUCT, &iSize)) == NULL)
    {
        NetPrintf(("protossl: _AsnParseCertificate: could not get subject\n"));
        return(-12);
    }
    pData = _AsnParseIdent(pData, iSize, &pCert->Subject);

    // parse the public key
    if ((pData = _AsnParseHeaderType(pData, pInfSkip, ASN_TYPE_SEQN+ASN_CONSTRUCT, &iSize)) == NULL)
    {
        NetPrintf(("protossl: _AsnParseCertificate: could not get public key\n"));
        return(-13);
    }

    // find the key algorithm sequence
    if ((pData = _AsnParseHeaderType(pData, pInfSkip, ASN_TYPE_SEQN+ASN_CONSTRUCT, &iSize)) == NULL)
    {
        NetPrintf(("protossl: _AsnParseCertificate: could not get key algorithm sequence\n"));
        return(-14);
    }
    pKeySkip = pData+iSize;

    // grab the public key algorithm
    if ((pData = _AsnParseHeaderType(pData, pKeySkip, ASN_TYPE_OBJECT+ASN_PRIMITIVE, &iSize)) == NULL)
    {
        NetPrintf(("protossl: _AsnParseCertificate: could not get public key algorithm\n"));
        return(-15);
    }
    pCert->iKeyType = _AsnParseObject(pData, iSize);

    // if ecdsa, grab the curve type before we move past the public key object
    if (pCert->iKeyType == ASN_OBJ_ECDSA_KEY)
    {
        if ((pData = _AsnParseHeaderType(pData+iSize, pKeySkip, ASN_TYPE_OBJECT+ASN_PRIMITIVE, &iSize)) == NULL)
        {
            NetPrintf(("protossl: _AsnParseCertificate: could not get ecdsa curve type\n"));
            return(-15);
        }
        pCert->iCrvType = _AsnParseObject(pData, iSize);
    }

    // find the actual public key
    if (((pData = _AsnParseHeaderType(pKeySkip, pLast, ASN_TYPE_BITSTRING+ASN_PRIMITIVE, &iSize)) == NULL) || (iSize < 1))
    {
        NetPrintf(("protossl: _AsnParseCertificate: could not get actual public key\n"));
        return(-16);
    }

    // skip the "extra bits" indicator and save keydata ref
    pKeyData = pData+1;
    iKeySize = iSize-1;
    pData += iSize;

    // parse optional info object, if present; see https://tools.ietf.org/html/rfc5280#section-4.1 
    if (((pData = _AsnParseHeaderType(pData, pInfSkip, ASN_EXPLICIT_TAG+3, &iSize)) != NULL) && (_AsnParseOptional(pData, iSize, pCert) < 0))
    {
        return(-17);
    }

    // parse signature algorithm sequence
    if ((pData = _AsnParseHeaderType(pInfSkip, pLast, ASN_TYPE_SEQN+ASN_CONSTRUCT, &iSize)) == NULL)
    {
        NetPrintf(("protossl: _AsnParseCertificate: could not get signature algorithm sequence\n"));
        return(-18);
    }
    pSigSkip = pData+iSize;

    // parse signature algorithms
    if ((pData = _AsnParseSignatureAlgorithms(pData, pLast, pCert)) == NULL)
    {
        return(-19);
    }

    // parse the signature data
    if ((pCert->iSigType == ASN_OBJ_ECDSA_SHA256) || (pCert->iSigType == ASN_OBJ_ECDSA_SHA384) || (pCert->iSigType == ASN_OBJ_ECDSA_SHA512))
    {
        // find the bitstring object
        if ((pData = _AsnParseHeaderType(pSigSkip, pLast, ASN_TYPE_BITSTRING+ASN_PRIMITIVE, &iSize)) == NULL)
        {
            NetPrintf(("protossl: _AsnParseCertificate: could not get signature algorithm identifier\n"));
            return(-20);
        }
        // skip leading pad byte; note we assume it is zero (no padding)
        pData += 1;
        iSize -= 1;
        // save signature object data; we will parse it later
        ds_memcpy_s(pCert->SigData, sizeof(pCert->SigData), pData, iSize);
        pCert->iSigSize = iSize;
    }
    else if ((pData = _AsnParseBinary(pSigSkip, pLast, ASN_TYPE_BITSTRING+ASN_PRIMITIVE, pCert->SigData, sizeof(pCert->SigData), &pCert->iSigSize, "signature data")) == NULL)
    {
        return(-21);
    }

    // parse the public key data (extract modulus & exponent)
    if ((pCert->iKeyType == ASN_OBJ_RSA_PKCS_KEY) || (pCert->iKeyType == ASN_OBJ_RSASSA_PSS))
    {
        pLast = pKeyData+iKeySize;

        // parse the sequence
        if ((pData = _AsnParseHeaderType(pKeyData, pLast, ASN_TYPE_SEQN+ASN_CONSTRUCT, &iSize)) == NULL)
        {
            NetPrintf(("protossl: _AsnParseCertificate: could not get public key sequence\n"));
            return(-22);
        }
        // parse the key modulus
        if ((pData = _AsnParseBinary(pData, pLast, ASN_TYPE_INTEGER+ASN_PRIMITIVE, pCert->KeyModData, sizeof(pCert->KeyModData), &pCert->iKeyModSize, "key modulus")) == NULL)
        {
            return(-23);
        }
        #if DEBUG_MOD_PRNT
        NetPrintMem(pCert->KeyModData, pCert->iKeyModSize, "public key modulus");
        #endif
        // parse the key exponent
        if ((pData = _AsnParseBinary(pData, pLast, ASN_TYPE_INTEGER+ASN_PRIMITIVE, pCert->KeyExpData, sizeof(pCert->KeyExpData), &pCert->iKeyExpSize, "key exponent")) == NULL)
        {
            return(-24);
        }
    }
    else if (pCert->iKeyType == ASN_OBJ_ECDSA_KEY)
    {
        // copy the curve data; unlike rsa, keydata points to the actual data
        ds_memcpy_s(pCert->KeyModData, sizeof(pCert->KeyModData), pKeyData, iKeySize);
        pCert->iKeyModSize = iKeySize;
        #if DEBUG_MOD_PRNT
        NetPrintMem(pCert->KeyModData, pCert->iKeyModSize, "public key curve data");
        #endif
    }
    else
    {
        NetPrintf(("protossl: _AsnParseCertificate: unrecognized key type %d\n", pCert->iKeyType));
        return(-25);
    }

    // get cert hash type
    pCert->eHashType = _AsnGetHashType(pCert->iSigHash);

    // generate the certificate hash
    if ((pHash = CryptHashGet(pCert->eHashType)) != NULL)
    {
        // do the hash
        uint8_t aContext[CRYPTHASH_MAXSTATE];
        pHash->Init(aContext, pHash->iHashSize);
        pHash->Update(aContext, pInfData, (int32_t)(pInfSkip-pInfData));
        pHash->Final(aContext, pCert->HashData,  pHash->iHashSize);
        // save hash size
        pCert->iHashSize = pHash->iHashSize;
    }
    else
    {
        NetPrintf(("protossl: could not get hash type %d\n", pCert->iSigHash));
        return(-26);
    }

    // certificate parsed successfully
    return(0);
}

/*F********************************************************************************/
/*!
    \Function _AsnParsePrivateKeyRSA

    \Description
        Parse public key modulus and public or private key exponent from private
        key certificate.

    \Input *pPrivateKeyData - private key certificate data in base64 form
    \Input iPrivateKeyLen   - length of private key certificate data
    \Input *pPrivateKey     - [out] parsed private key data

    \Output
        int32_t             - private key certificate binary (decoded) size, or negative on error

    \Notes
        \verbatim
            RSAPrivateKey ::= SEQUENCE {
              version Version,
              modulus INTEGER,
              publicExponent INTEGER,
              privateExponent INTEGER,
              primeP INTEGER,
              primeQ INTEGER,
              exponentP INTEGER,
              exponentQ INTEGER,
              coefficient INTEGER
            }
        \endverbatim

    \Version 03/15/2012 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _AsnParsePrivateKeyRSA(const char *pPrivateKeyData, int32_t iPrivateKeyLen, X509PrivateKeyT *pPrivateKey)
{
    int32_t iPrivKeySize, iSize, iSize2, iTemp = 0;
    const uint8_t *pData, *pData2;

    // clear output structure
    ds_memclr(pPrivateKey, sizeof(*pPrivateKey));

    // base64 decode into buffer
    if ((iPrivKeySize = Base64Decode3(pPrivateKeyData, iPrivateKeyLen, pPrivateKey->strPrivKeyData, sizeof(pPrivateKey->strPrivKeyData))) == 0)
    {
        NetPrintf(("protossl: _AsnParsePrivateKey: could not decode private key\n"));
        return(-2);
    }
    // parse the sequence
    if ((pData = _AsnParseHeaderType((uint8_t *)pPrivateKey->strPrivKeyData, (uint8_t *)pPrivateKey->strPrivKeyData+iPrivKeySize, ASN_TYPE_SEQN+ASN_CONSTRUCT, &iSize)) == NULL)
    {
        NetPrintf(("protossl: _AsnParsePrivateKey: could not get private key sequence\n"));
        return(-3);
    }
    // skip the version
    if ((pData = _AsnParseBinary(pData, pData+iSize, ASN_TYPE_INTEGER+ASN_PRIMITIVE, NULL, 0, &iTemp, "version")) == NULL)
    {
        return(-4);
    }
    // check for sequence... if there is one, this is an RSA-PSS PKCS#8 certificate with some additional stuff we need to skip
    if ((pData2 = _AsnParseHeaderType(pData, pData+iSize, ASN_TYPE_SEQN+ASN_CONSTRUCT, &iSize2)) != NULL)
    {
        if ((pData = _AsnParseHeaderType(pData2, pData2+iSize2, ASN_TYPE_OBJECT+ASN_PRIMITIVE, &iSize2)) == NULL)
        {
            return(-5);
        }
        if (_AsnParseObject(pData, iSize2) != ASN_OBJ_RSASSA_PSS)
        {
            return(-6);
        }
        pData += iSize2;
        if ((pData = _AsnParseHeaderType(pData, pData+iSize, ASN_TYPE_OCTSTRING+ASN_PRIMITIVE, &iSize)) == NULL)
        {
            return(-7);
        }
        if ((pData = _AsnParseHeaderType(pData, pData+iSize, ASN_TYPE_SEQN+ASN_CONSTRUCT, &iSize)) == NULL)
        {
            return(-8);
        }
        if ((pData = _AsnParseBinary(pData, pData+iSize, ASN_TYPE_INTEGER+ASN_PRIMITIVE, NULL, 0, &iTemp, "zero")) == NULL)
        {
            return(-9);
        }
    }
    // parse public key modulus
    if ((pData = _AsnParseBinaryPtr(pData, pData+iSize, ASN_TYPE_INTEGER+ASN_PRIMITIVE, &pPrivateKey->Modulus.pObjData, &pPrivateKey->Modulus.iObjSize, "key modulus")) == NULL)
    {
        return(-10);
    }
    // parse public key exponent
    if ((pData = _AsnParseBinaryPtr(pData, pData+iSize, ASN_TYPE_INTEGER+ASN_PRIMITIVE, &pPrivateKey->PublicExponent.pObjData, &pPrivateKey->PublicExponent.iObjSize, "public key exponent")) == NULL)
    {
        return(-11);
    }
    // parse private key exponent
    if ((pData = _AsnParseBinaryPtr(pData, pData+iSize, ASN_TYPE_INTEGER+ASN_PRIMITIVE, &pPrivateKey->PrivateExponent.pObjData, &pPrivateKey->PrivateExponent.iObjSize, "private key exponent")) == NULL)
    {
        return(-12);
    }
    // parse primeP
    if ((pData = _AsnParseBinaryPtr(pData, pData+iSize, ASN_TYPE_INTEGER+ASN_PRIMITIVE, &pPrivateKey->PrimeP.pObjData, &pPrivateKey->PrimeP.iObjSize, "primeP")) == NULL)
    {
        return(-13);
    }
    // parse primeQ
    if ((pData = _AsnParseBinaryPtr(pData, pData+iSize, ASN_TYPE_INTEGER+ASN_PRIMITIVE, &pPrivateKey->PrimeQ.pObjData, &pPrivateKey->PrimeQ.iObjSize, "primeQ")) == NULL)
    {
        return(-14);
    }
    // parse exponentP
    if ((pData = _AsnParseBinaryPtr(pData, pData+iSize, ASN_TYPE_INTEGER+ASN_PRIMITIVE, &pPrivateKey->ExponentP.pObjData, &pPrivateKey->ExponentP.iObjSize, "exponentP")) == NULL)
    {
        return(-15);
    }
    // parse exponentQ
    if ((pData = _AsnParseBinaryPtr(pData, pData+iSize, ASN_TYPE_INTEGER+ASN_PRIMITIVE, &pPrivateKey->ExponentQ.pObjData, &pPrivateKey->ExponentQ.iObjSize, "exponentQ")) == NULL)
    {
        return(-16);
    }
    // parse coefficient
    if ((pData = _AsnParseBinaryPtr(pData, pData+iSize, ASN_TYPE_INTEGER+ASN_PRIMITIVE, &pPrivateKey->Coefficient.pObjData, &pPrivateKey->Coefficient.iObjSize, "coefficient")) == NULL)
    {
        return(-17);
    }
    return(iPrivKeySize);
}

/*F********************************************************************************/
/*!
    \Function _AsnParsePrivateKeyEcdsa

    \Description
        Parse public key modulus and public or private key exponent from private
        key certificate.

    \Input *pPrivateKeyData - private key certificate data in base64 form
    \Input iPrivateKeyLen   - length of private key certificate data
    \Input *pPrivateKey     - [out] parsed private key data

    \Output
        int32_t             - private key certificate binary (decoded) size, or negative on error

    \Notes
    
        ref: https://tools.ietf.org/html/rfc5915#section-3
    
        ECPrivateKey ::= SEQUENCE {
            version        INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1),
            privateKey     OCTET STRING,
            parameters [0] ECParameters {{ NamedCurve }} OPTIONAL,
            publicKey  [1] BIT STRING OPTIONAL
        }

    \Version 03/09/2018 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _AsnParsePrivateKeyEcdsa(const char *pPrivateKeyData, int32_t iPrivateKeyLen, X509PrivateKeyT *pPrivateKey)
{
    int32_t iPrivKeySize, iSize, iTemp = 0;
    const uint8_t *pData;

    // clear output structure
    ds_memclr(pPrivateKey, sizeof(*pPrivateKey));

    // decode private key into private key buffer
    if ((iPrivKeySize = Base64Decode3(pPrivateKeyData, iPrivateKeyLen, pPrivateKey->strPrivKeyData, sizeof(pPrivateKey->strPrivKeyData))) == 0)
    {
        NetPrintf(("protossl: _AsnParsePrivateKey: could not decode private key\n"));
        return(-2);
    }
    // parse the sequence
    if ((pData = _AsnParseHeaderType((uint8_t *)pPrivateKey->strPrivKeyData, (uint8_t *)pPrivateKey->strPrivKeyData+iPrivKeySize, ASN_TYPE_SEQN+ASN_CONSTRUCT, &iSize)) == NULL)
    {
        NetPrintf(("protossl: _AsnParsePrivateKey: could not get private key sequence\n"));
        return(-3);
    }
    // skip the version
    if ((pData = _AsnParseBinary(pData, pData+iSize, ASN_TYPE_INTEGER+ASN_PRIMITIVE, NULL, 0, &iTemp, "version")) == NULL)
    {
        return(-4);
    }
    // parse the private key; we store it in the Modulus field
    if ((pData = _AsnParseBinaryPtr(pData, pData+iSize, ASN_TYPE_OCTSTRING+ASN_PRIMITIVE, &pPrivateKey->Modulus.pObjData, &pPrivateKey->Modulus.iObjSize, "key modulus")) == NULL)
    {
        return(-5);
    }
    return(iPrivKeySize);
}

/*
    asn.1 writing
*/

/*F********************************************************************************/
/*!
    \Function _AsnWriteBn

    \Description
        Writes a BigNumber as an ASN.1 object

    \Input *pBuffer     - [out] storage for generated object
    \Input iBufSize     - size of output buffer
    \Input *pBn         - BigNumber to write
    \Input uAsnType     - ASN.1 object type to write

    \Output
        uint8_t *       - pointer past end of written object

    \Version 03/13/2018 (jbrookes)
*/
/********************************************************************************F*/
static uint8_t *_AsnWriteBn(uint8_t *pBuffer, int32_t iBufSize, const CryptBnT *pBn, uint8_t uAsnType)
{
    int32_t iByteLen = CryptBnByteLen(pBn);
    uint8_t *pSize;
    *pBuffer++ = uAsnType;
    pSize = pBuffer++;
    if ((uAsnType & (ASN_TYPE_BITSTRING|ASN_TYPE_INTEGER)) && (CryptBnBitTest(pBn, (iByteLen*8)-1)))
    {
        *pBuffer++ = 0;
    }
    CryptBnFinal(pBn, pBuffer, iByteLen);
    pBuffer += iByteLen;
    *pSize = pBuffer-pSize-1;
    return(pBuffer);
}

/*F********************************************************************************/
/*!
    \Function _AsnWriteSignatureEcdsa

    \Description
        Writes an ASN.1 Elliptic Curve signature

    \Input *pSigData    - [out] storage for generated ASN.1 encoded signature
    \Input iSigSize     - size of output buffer
    \Input *pSignature  - signature to write

    \Output
        int32_t         - size of written signature

    \Version 03/13/2018 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _AsnWriteSignatureEcdsa(uint8_t *pSigData, int32_t iSigSize, CryptEccPointT *pSignature)
{
    uint8_t *pSigOut = pSigData, *pSigLen;
    // write the signature to output buffer
    *pSigOut++ = ASN_CONSTRUCT|ASN_TYPE_SEQN;
    // save pointer to length
    pSigLen = pSigOut++;
    // add signature.x
    pSigOut = _AsnWriteBn(pSigOut, iSigSize, &pSignature->X, ASN_TYPE_INTEGER|ASN_PRIMITIVE);
    // add signature.y
    pSigOut = _AsnWriteBn(pSigOut, iSigSize, &pSignature->Y, ASN_TYPE_INTEGER|ASN_PRIMITIVE);
    // write construct length (does not include type or length)
    *pSigLen = pSigOut-pSigData-2;
    // return total size
    return(*pSigLen+2);
}

/*F********************************************************************************/
/*!
    \Function _AsnWriteDigitalHashObject

    \Description
        Generate a DigitalHash object

    \Input *pBuffer     - [out] storage for generated digitalhash object
    \Input iBufSize     - size of output buffer
    \Input *pHashData   - pointer to hash data to embed in the object (may be NULL)
    \Input iHashSize    - size of hash data
    \Input eHashType    - type of hash

    \Output
        int32_t         - size of generated object

    \Version 04/30/2017 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _AsnWriteDigitalHashObject(uint8_t *pBuffer, int32_t iBufSize, const uint8_t *pHashData, int32_t iHashSize, CryptHashTypeE eHashType)
{
    const ASNObjectT *pObject;
    uint8_t *pBufStart = pBuffer;

    // make sure there's space
    if (iBufSize < (CRYPTHASH_MAXDIGEST+64))
    {
        NetPrintf(("protossl: insufficient space for digital hash object\n"));
        return(0);
    }
    // get ASN object to encode
    if ((pObject = _AsnGetObject(_SSL3_CrypttoASN[eHashType])) == NULL)
    {
        NetPrintf(("protossl: failed to get digital hash ASN object\n"));
        return(0);
    }

    /* generate DER-encoded DigitalHash object as per http://tools.ietf.org/html/rfc5246#section-4.7; note that
       this code assumes the total object size is 127 bytes or fewer as it uses single-byte length encoding.
       format defined by https://tools.ietf.org/html/rfc8017#section-9.2 */
    *pBuffer++ = ASN_CONSTRUCT | ASN_TYPE_SEQN;
    *pBuffer++ = (2) + (2 + pObject->iSize) + (2) + (2 + iHashSize);
    *pBuffer++ = ASN_CONSTRUCT | ASN_TYPE_SEQN;
    *pBuffer++ = (2 + pObject->iSize) + (2);
    // add hash object
    *pBuffer++ = ASN_TYPE_OBJECT;
    *pBuffer++ = pObject->iSize;
    // add asn.1 object identifier for hash
    ds_memcpy(pBuffer, pObject->strData, pObject->iSize);
    pBuffer += pObject->iSize;
    // add null object
    *pBuffer++ = ASN_TYPE_NULL;
    *pBuffer++ = 0x00;
    // add hash object (octet string)
    *pBuffer++ = ASN_TYPE_OCTSTRING;
    *pBuffer++ = iHashSize;
    // add hash data
    if (pHashData != NULL)
    {
        ds_memcpy(pBuffer, pHashData, iHashSize);
        pBuffer += iHashSize;
    }

    // return final object size
    return(pBuffer - pBufStart);
}

/*
    certificate functions
*/

/*F********************************************************************************/
/*!
    \Function _CertificateFindSignature

    \Description
        Find PEM signature in the input data, if it exists, and return a
        pointer to the encapsulated data.

    \Input *pCertData       - pointer to data to scan
    \Input iCertSize        - size of input data
    \Input *pSigText        - signature text to find

    \Output
        const uint8_t *     - pointer to data, or null if not found

    \Version 01/14/2009 (jbrookes)
*/
/********************************************************************************F*/
static const uint8_t *_CertificateFindSignature(const uint8_t *pCertData, int32_t iCertSize, const char *pSigText)
{
    int32_t iSigLen = (int32_t)strlen(pSigText);
    int32_t iCertIdx;

    for (iCertIdx = 0; iCertIdx < iCertSize; iCertIdx += 1)
    {
        if ((pCertData[iCertIdx] == *pSigText) && ((iCertSize - iCertIdx) >= iSigLen))
        {
            if (!strncmp((const char *)pCertData+iCertIdx, pSigText, iSigLen))
            {
                return(pCertData+iCertIdx);
            }
        }
    }
    return(NULL);
}

/*F********************************************************************************/
/*!
    \Function _CertificateFindData

    \Description
        Finds PEM signature in the input data, if it exists, and stores the
        offsets to the beginning and end of the embedded signature data.

    \Input *pCertData       - pointer to data to scan
    \Input iCertSize        - size of input data
    \Input **pCertBeg       - [out] storage for index to beginning of certificate data
    \Input **pCertEnd       - [out] storage for index to end of certificate data
    \Input *pCertType       - [out] storage for certificate type (ASN_OBJ_\*)

    \Output
        int32_t             - 0=did not find certificate, else did find certificate

    \Version 01/14/2009 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _CertificateFindData(const uint8_t *pCertData, int32_t iCertSize, const uint8_t **pCertBeg, const uint8_t **pCertEnd, uint32_t *pCertType)
{
    const CertificateSignatureT *pCertSig;
    int32_t iCertSig;

    for (iCertSig = 0; iCertSig < (signed)(sizeof(_SSL3_CertSignatures)/sizeof(_SSL3_CertSignatures[0])); iCertSig += 1)
    {
        pCertSig = &_SSL3_CertSignatures[iCertSig];

        // make sure "end-cert" occurs after start since we support bundles
        if (((*pCertBeg = _CertificateFindSignature(pCertData, iCertSize, pCertSig->pCertBeg)) != NULL) &&
            ((*pCertEnd = _CertificateFindSignature(*pCertBeg, (int32_t)(pCertData+iCertSize-*pCertBeg), pCertSig->pCertEnd)) != NULL))
        {
            // skip begin signature
            *pCertBeg += strlen(pCertSig->pCertBeg);
            // write cert type
            *pCertType = pCertSig->uCertType;
            // return size to caller
            return((int32_t)(*pCertEnd-*pCertBeg));
        }
    }

    // if no signature, assume it's just straight base64 pkcs1
    *pCertBeg = pCertData;
    *pCertEnd = pCertData + iCertSize;
    *pCertType = ASN_OBJ_RSA_PKCS_KEY;
    return(iCertSize);
}

/*F********************************************************************************/
/*!
    \Function _CertificateDecodePrivate

    \Description
        Parse public key modulus and public or private key exponent from private
        key certificate.

    \Input *pPrivateKeyData - private key certificate data in base64 form
    \Input iPrivateKeyLen   - length of private key certificate data
    \Input *pPrivateKey     - [out] parsed private key data

    \Output
        int32_t             - private key certificate binary (decoded) size, or negative on error

    \Version 03/09/2018 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _CertificateDecodePrivate(const char *pPrivateKeyData, int32_t iPrivateKeyLen, X509PrivateKeyT *pPrivateKey)
{
    const uint8_t *pCertBeg, *pCertEnd;
    int32_t iCertSize, iResult;
    uint32_t uCertType;

    // make sure we have key data first
    if (pPrivateKeyData == NULL)
    {
        NetPrintf(("protossl: _AsnParsePrivateKey: no private key set\n"));
        return(-1);
    }

    // find the base64-encoded certificate data
    iCertSize = _CertificateFindData((const uint8_t *)pPrivateKeyData, iPrivateKeyLen, &pCertBeg, &pCertEnd, &uCertType);

    // parse ecdsa or rsa?
    if (uCertType == ASN_OBJ_ECDSA_KEY)
    {
        iResult = _AsnParsePrivateKeyEcdsa((const char *)pCertBeg, iCertSize, pPrivateKey);
    }
    else
    {
        iResult = _AsnParsePrivateKeyRSA((const char *)pCertBeg, iCertSize, pPrivateKey);
    }
    return(iResult);
}

/*F********************************************************************************/
/*!
    \Function _CertificateDecodePublic

    \Description
        Decode the specified PEM certificate into binary format, parse and extract some information

    \Input *pState      - module state reference
    \Input *pPemData    - certificate to decode
    \Input iPemSize     - certificate length

    \Output
        CertificateDataT * - pointer to new certificate data, or null

    \Version 03/09/2018 (jbrookes)
*/
/********************************************************************************F*/
static CertificateDataT *_CertificateDecodePublic(ProtoSSLRefT *pState, const uint8_t *pPemData, int32_t iPemSize)
{
    const uint8_t *pCertBeg, *pCertEnd;
    CertificateDataT *pCertificate = NULL;
    X509CertificateT Cert;
    int32_t iCertSize;
    uint32_t uCertType;

    // find the base64-encoded certificate data
    iPemSize = _CertificateFindData(pPemData, iPemSize, &pCertBeg, &pCertEnd, &uCertType);
    // get decoded size
    iCertSize = Base64Decode3((const char *)pCertBeg, iPemSize, NULL, 0x7fffffff);
    // allocate certificate buffer
    if ((iCertSize > 0) && ((pCertificate = DirtyMemAlloc(sizeof(*pCertificate)+iCertSize, PROTOSSL_MEMID, pState->iMemGroup, pState->pMemGroupUserData)) != NULL))
    {
        // base64 decode to binary 
        Base64Decode3((const char *)pCertBeg, iPemSize, (char *)pCertificate->aCertData, iCertSize);
        // parse the certificate
        _AsnParseCertificate(&Cert, pCertificate->aCertData, iCertSize);
        // save some cert info
        pCertificate->iCertSize = iCertSize;
        pCertificate->iCrvType = Cert.iCrvType;
        pCertificate->iKeyType = Cert.iKeyType;
        pCertificate->iSigType = Cert.iSigType;
    }
    return(pCertificate);
}

#if DIRTYCODE_LOGGING
/*F********************************************************************************/
/*!
    \Function _CertificateDebugFormatIdent

    \Description
        Print cert ident info to debug output.

    \Input *pIdent  - cert ident info
    \Input *pStrBuf - [out] buffer to format cert ident into
    \Input iBufLen  - length of buffer

    \Output
        char *      - pointer to result string

    \Version 01/13/2009 (jbrookes)
*/
/********************************************************************************F*/
static char *_CertificateDebugFormatIdent(const ProtoSSLCertIdentT *pIdent, char *pStrBuf, int32_t iBufLen)
{
    ds_snzprintf(pStrBuf, iBufLen, "C=%s, ST=%s, L=%s, O=%s, OU=%s, CN=%s",
        pIdent->strCountry, pIdent->strState, pIdent->strCity,
        pIdent->strOrg, pIdent->strUnit, pIdent->strCommon);
    return(pStrBuf);
}
#else
#define _CertificateDebugFormatIdent(__pIdent, __pStrBuf, __iBufLen)
#endif

#if DIRTYCODE_LOGGING
/*F********************************************************************************/
/*!
    \Function _CertificateDebugPrint

    \Description
        Print cert ident info to debug output.

    \Input *pCert       - cert to print debug info for
    \Input *pMessage    - string identifier (may be null)

    \Version 01/13/2009 (jbrookes)
*/
/********************************************************************************F*/
static void _CertificateDebugPrint(const X509CertificateT *pCert, const char *pMessage)
{
    char strCertIdent[1024];
    #if DEBUG_VAL_CERT
    char strTimeFrom[32], strTimeTill[32];
    char strMaxHeight[32];
    #endif
    if (pMessage != NULL)
    {
        NetPrintf(("protossl: %s\n", pMessage));
    }
    NetPrintf(("protossl:  issuer: %s\n", _CertificateDebugFormatIdent(&pCert->Issuer, strCertIdent, sizeof(strCertIdent))));
    NetPrintf(("protossl: subject: %s\n", _CertificateDebugFormatIdent(&pCert->Subject, strCertIdent, sizeof(strCertIdent))));
    #if DEBUG_VAL_CERT
    NetPrintf(("protossl:   valid: from %s to %s\n",
            ds_secstostr(pCert->uGoodFrom, TIMETOSTRING_CONVERSION_RFC_0822, FALSE, strTimeFrom, sizeof(strTimeFrom)),
            ds_secstostr(pCert->uGoodTill, TIMETOSTRING_CONVERSION_RFC_0822, FALSE, strTimeTill, sizeof(strTimeTill))));
    NetPrintf(("protossl: keytype: %d\n", pCert->iKeyType));
    NetPrintf(("protossl: keyexp size: %d\n", pCert->iKeyExpSize));
    NetPrintf(("protossl: keymod size: %d\n", pCert->iKeyModSize));
    NetPrintf(("protossl: sigtype: %d\n", pCert->iSigType));
    NetPrintf(("protossl: sigsize: %d\n", pCert->iSigSize));
    ds_snzprintf(strMaxHeight, sizeof(strMaxHeight), "%d", pCert->iMaxHeight - 1);
    NetPrintf(("protossl: CertIsCA(%d): pathLenConstraints(%s)\n", pCert->iCertIsCA, (pCert->iMaxHeight == 0) ? "unlimited" : strMaxHeight));
    #endif
}
#else
#define _CertificateDebugPrint(__pCert, __pMessage)
#endif

/*F********************************************************************************/
/*!
    \Function _CertificateSetFailureInfo

    \Description
        Set the certificate info (used on failure)

    \Input *pState      - module state (may be NULL)
    \Input *pCert       - pointer to certificate to fill in from header data (may be NULL)
    \Input bIssuer      - if TRUE copy issuer, else subject

    \Version 01/24/2012 (szhu)
*/
/********************************************************************************F*/
static void _CertificateSetFailureInfo(ProtoSSLRefT *pState, X509CertificateT *pCert, uint8_t bIssuer)
{
    if ((pState != NULL) && (pCert != NULL) && (pState->bCertInfoSet == FALSE))
    {
        const ProtoSSLCertIdentT *pCertIdent = bIssuer ? &pCert->Issuer : &pCert->Subject;
        ds_memcpy_s(&pState->CertInfo.Ident, sizeof(pState->CertInfo.Ident), pCertIdent, sizeof(*pCertIdent));
        pState->CertInfo.iKeyModSize = pCert->iSigSize; // CACert::KeyModSize == Cert::SigSize
        pState->bCertInfoSet = TRUE;
    }
}

/*F********************************************************************************/
/*!
    \Function _CertificateCompareIdent

    \Description
        Compare two identities

    \Input *pIdent1     - pointer to ProtoSSLCertIdentT structure
    \Input *pIdent2     - pointer to ProtoSSLCertIdentT structure
    \Input bMatchUnit   - if TRUE compare the Unit string

    \Output
        int32_t         - zero=match, non-zero=no-match

    \Notes
        Unit is considered an optional match criteria by openssl.

    \Version 03/11/2009 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _CertificateCompareIdent(const ProtoSSLCertIdentT *pIdent1, const ProtoSSLCertIdentT *pIdent2, uint8_t bMatchUnit)
{
    int32_t iResult;

    #if DEBUG_VAL_CERT
    char strIdent1[256], strIdent2[256];
    _CertificateDebugFormatIdent(pIdent1, strIdent1, sizeof(strIdent1));
    _CertificateDebugFormatIdent(pIdent2, strIdent2, sizeof(strIdent2));
    NetPrintf(("protossl: comparing '%s' to '%s'\n", strIdent2, strIdent1));
    #endif

    iResult  = strcmp(pIdent1->strCountry, pIdent2->strCountry) != 0;
    iResult += strcmp(pIdent1->strState, pIdent2->strState) != 0;
    iResult += strcmp(pIdent1->strCity, pIdent2->strCity) != 0;
    iResult += strcmp(pIdent1->strOrg, pIdent2->strOrg) != 0;
    iResult += strcmp(pIdent1->strCommon, pIdent2->strCommon) != 0;
    if (bMatchUnit)
    {
        iResult += strcmp(pIdent1->strUnit, pIdent2->strUnit) != 0;
    }
    return(iResult);
}

/*F********************************************************************************/
/*!
    \Function _CertificateMatchHostname

    \Description
        Perform wildcard case-insensitive string comparison of given input
        strings.  This implementation assumes the first string does not
        include a wildcard character and the second string includes exactly
        zero or one wildcard characters, which must be an asterisk.  The
        wildcard is intentionally limited as per the notes below to only
        match a single domain fragment.

    \Input *pString1    - first string to compare
    \Input *pString2    - second string to compare

    \Output
        int32_t         - like strcmp()

    \Notes
       As specified per RFC 2818:

       \verbatim
       Matching is performed using the matching rules specified by
       [RFC2459].  If more than one identity of a given type is present in
       the certificate (e.g., more than one dNSName name, a match in any one
       of the set is considered acceptable.) Names may contain the wildcard
       character * which is considered to match any single domain name
       component or component fragment. E.g., *.a.com matches foo.a.com but
       not bar.foo.a.com. f*.com matches foo.com but not bar.com.
       \endverbatim

    \Version 03/19/2009 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _CertificateMatchHostname(const char *pString1, const char *pString2)
{
    int32_t r;
    char c1, c2;

    do {
        c1 = *pString1++;
        if ((c1 >= 'A') && (c1 <= 'Z'))
            c1 ^= 32;
        c2 = *pString2;
        if ((c2 >= 'A') && (c2 <= 'Z'))
            c2 ^= 32;
        if ((c2 == '*') && (c1 != '.') && (c1 != '\0'))
        {
            r = _CertificateMatchHostname(pString1, pString2+1);
            if (r == 0)
            {
                break;
            }
            r = 0;
        }
        else
        {
            pString2 += 1;
            r = c1-c2;
        }
    } while ((c1 != 0) && (c2 != 0) && (r == 0));

    return(r);
}

/*F********************************************************************************/
/*!
    \Function _CertificateMatchSubjectAlternativeName

    \Description
        Takes as input a hostname and Subject Alternative Name object,
        returns zero if the hostname matches a SAN object entry and non-zero
        otherwise.

    \Input *pStrHost    - pointer to hostname to compare against
    \Input *pSubject    - pointer to subject alternative object
    \Input iSubjectAltLen - length of subject alternative object

    \Output
        int32_t         - like strcmp()

    \Version 11/01/2012 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _CertificateMatchSubjectAlternativeName(const char *pStrHost, const uint8_t *pSubject, int32_t iSubjectAltLen)
{
    const uint8_t *pSubjectEnd = pSubject + iSubjectAltLen;
    char strCompare[256], *pStrCompare;
    int32_t iType, iSize;

    // parse the wrapper object
    pSubject = _AsnParseHeader(pSubject, pSubjectEnd, &iType, &iSize);

    // check against subject alternative strings
    for (pSubjectEnd = pSubject + iSubjectAltLen; pSubject != NULL; pSubject += iSize)
    {
        // parse the object
        pSubject = _AsnParseHeader(pSubject, pSubjectEnd, &iType, &iSize);

        // if it's not a dnsname object, skip it
        if (iType != ASN_IMPLICIT_TAG+2)
        {
            continue;
        }

        // make a copy of the comparison string
        ds_strsubzcpy(strCompare, sizeof(strCompare), (const char *)pSubject, iSize);

        // skip leading whitespace, if any
        for (pStrCompare = strCompare; ((*pStrCompare > 0) && (*pStrCompare <= ' ')); pStrCompare += 1)
            ;

        // do the name compare
        if (_CertificateMatchHostname(pStrHost, pStrCompare) == 0)
        {
            return(0);
        }
    }

    return(-1);
}

/*F********************************************************************************/
/*!
    \Function _CertificateGetSigAlg

    \Description
        Get signature algorithm to use based on certificate key type

    \Input *pCertificate        - certificate to get signature algorithm for

    \Output
        uint32_t uCertSigAlg    - signature algorithm

    \Version 05/12/2017 (jbrookes)
*/
/********************************************************************************F*/
static uint32_t _CertificateGetSigAlg(const CertificateDataT *pCertificate)
{
    uint32_t uCertSigAlg = SSL3_SIGALG_NONE;
    if (pCertificate != NULL)
    {
        uCertSigAlg = (pCertificate->iKeyType == ASN_OBJ_ECDSA_KEY) ? SSL3_SIGALG_ECDSA : SSL3_SIGALG_RSA;
    }
    return(uCertSigAlg);
}

/*
    pkcs1 verification and generation
*/

/*F********************************************************************************/
/*!
    \Function _Pkcs1VerifyRSAES

    \Description
        Validate RSAES-PKCS1-v1_5 padding as defined by
        https://tools.ietf.org/html/rfc8017#section-7.2.

    \Input *pData       - data to validate
    \Input iKeySize     - key modulus size, in bytes
    \Input iSecretSize  - premaster secret size, in bytes

    \Output
        int32_t         - zero=invalid, else valid

    \Version 02/18/2014 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _Pkcs1VerifyRSAES(const uint8_t *pData, int32_t iKeySize, int32_t iSecretSize)
{
    int32_t iIndex;
    uint8_t bValid = TRUE;

    // validate signature
    if ((pData[0] != 0) || (pData[1] != 2))
    {
        NetPrintf(("protossl: pkcs1 signature invalid\n"));
        bValid = FALSE;
    }
    // validate random padding; must be non-zero
    for (iIndex = 2; iIndex < iKeySize-iSecretSize-1; iIndex += 1)
    {
        if (pData[iIndex] == 0)
        {
            NetPrintf(("protossl: pkcs1 padding validation found zero byte\n"));
            bValid = FALSE;
            break;
        }
    }
    // validate zero byte before premaster key data
    if (pData[iKeySize-iSecretSize-1] != 0)
    {
        NetPrintf(("protossl: pkcs1 premaster key padding not zero\n"));
        bValid = FALSE;
    }
    // return result
    return(bValid);
}

/*F********************************************************************************/
/*!
    \Function _Pkcs1VerifyEMSA

    \Description
        As per https://tools.ietf.org/html/rfc5246#section-4.7 validate
        EMSA-PKCS1-v1_5 padding, and return pointer to signature hash if valid.
        Format defined by https://tools.ietf.org/html/rfc8017#section-9.2.

    \Input *pData       - data to validate
    \Input iSigSize     - signature size, in bytes
    \Input iHashSize    - signature hash size, in bytes
    \Input eHashType    - signature hash type (if CRYPTHASH_NULL, don't validate hash object)

    \Output
        uint8_t *       - pointer to signature data, or NULL if invalid

    \Version 04/27/2017 (jbrookes)
*/
/********************************************************************************F*/
static const uint8_t *_Pkcs1VerifyEMSA(const uint8_t *pData, int32_t iSigSize, int32_t iHashSize, CryptHashTypeE eHashType)
{
    uint8_t aHashObj[CRYPTHASH_MAXDIGEST+64];
    int32_t iIndex, iSize;

    // validate EMSA signature
    if ((pData[0] != 0) || (pData[1] != 1))
    {
        return(NULL);
    }
    // validate padding; must be 0xff
    for (iIndex = 2; (iIndex < (iSigSize-iHashSize)) && (pData[iIndex] == 0xff); iIndex += 1)
        ;
    // validate zero byte immediately preceding signature hash object
    if (pData[iIndex++] != 0)
    {
        return(NULL);
    }

    // validate the digital hash object that wraps the signature hash
    if (eHashType != CRYPTHASH_NULL)
    {
        // generate digitahash object we expect, and validate we got it
        if (((iSize = _AsnWriteDigitalHashObject(aHashObj, sizeof(aHashObj), NULL, iHashSize, eHashType)) == 0) || (memcmp(aHashObj, pData + iIndex, iSize)))
        {
            return(NULL);
        }
        iIndex += iSize;
    }

    // make sure we validated the entire signature, minus the hash itself
    if ((iIndex+iHashSize) != iSigSize)
    {
        return(NULL);
    }

    // return pointer to signature hash
    return(pData+iIndex);
}

/*F********************************************************************************/
/*!
    \Function _Pkcs1DoMGF1

    \Description
        Implements MFG1 as per https://tools.ietf.org/html/rfc8017#appendix-B.2.1 

    \Input *pOutput     - [out] output of mgf
    \Input iOutputLen   - length of output buf
    \Input *pInput      - mgf input
    \Input iInputLen    - length of mgf input
    \Input eHashType    - mgf hash type

    \Version 05/11/2017 (jbrookes)
*/
/********************************************************************************F*/
static void _Pkcs1DoMGF1(uint8_t *pOutput, int32_t iOutputLen, const uint8_t *pInput, int32_t iInputLen, CryptHashTypeE eHashType)
{
    uint8_t aHashState[CRYPTHASH_MAXSTATE], aCounter[4];
    const CryptHashT *pHash;
    int32_t iHashLen;
    uint32_t uRound;

    // get hash object and length
    if ((pHash = CryptHashGet(eHashType)) == NULL)
    {
        return;
    }
    iHashLen = CryptHashGetSize(eHashType);

    // do the hashing
    for (uRound = 0; iOutputLen > 0; uRound += 1, pOutput += iHashLen, iOutputLen -= iHashLen)
    {
        aCounter[0] = (uint8_t)(uRound >> 24);
        aCounter[1] = (uint8_t)(uRound >> 16);
        aCounter[2] = (uint8_t)(uRound >> 8);
        aCounter[3] = (uint8_t)(uRound);

        pHash->Init(aHashState, iHashLen);
        pHash->Update(aHashState, pInput, iHashLen);
        pHash->Update(aHashState, aCounter, sizeof(aCounter));
        pHash->Final(aHashState, pOutput, iHashLen);
    }
}

/*F********************************************************************************/
/*!
    \Function _Pkcs1VerifyEMSA_PSS

    \Description
        As per https://tools.ietf.org/html/rfc8017#section-9.1.2 validate
        EMSA-PSS envelope, and compare against the specified hash value.

    \Input *pSigData    - signature data
    \Input iSigSize     - signature size, in bytes
    \Input eSigHash     - signature hash
    \Input iSaltSize    - salt size, in bytes
    \Input *pMsgHash    - message hash data
    \Input iHashSize    - signature hash size, in bytes
    \Input eMgfHash     - mgf hash type

    \Output
        uint32_t        - zero=verify failure, else success

    \Notes
        This function deliberately uses coding standards and comments matching
        the specification to make it easier to compare while reading the specs.

        The maximum salt length supported is 128 bytes (2*CRYPTHASH_MAXDIGEST)

    \Version 05/11/2017 (jbrookes)
*/
/********************************************************************************F*/
static uint32_t _Pkcs1VerifyEMSA_PSS(const uint8_t *pSigData, int32_t iSigSize, CryptHashTypeE eSigHash, int32_t iSaltSize, const uint8_t *pMsgHash, int32_t iHashSize, CryptHashTypeE eMgfHash)
{
    uint8_t maskedDB[SSL_SIG_MAX], dbMask[SSL_SIG_MAX], DB[SSL_SIG_MAX], H[CRYPTHASH_MAXDIGEST], salt[CRYPTHASH_MAXDIGEST*2];
    const CryptHashT *pHash = CryptHashGet(eSigHash);
    uint8_t aHashState[CRYPTHASH_MAXSTATE];
    uint8_t Mprime[CRYPTHASH_MAXDIGEST*3 + 8], Hprime[CRYPTHASH_MAXDIGEST];
    int32_t iDB, iDBLen;
    uint8_t uCheck;

    // if the rightmost octet of EM does not have hexadecimal value 0xbc, output "inconsistent" and stop.
    if (pSigData[iSigSize-1] != 0xBC)
    {
        NetPrintf(("protossl: signature failed identity check\n"));
        return(0);
    }

    // let maskedDB be the leftmost emLen - hLen - 1 octets of EM, and let H be the next hLen octets.
    iDBLen = iSigSize - iHashSize - 1;
    ds_memcpy_s(maskedDB, sizeof(maskedDB), pSigData, iDBLen);
    ds_memcpy_s(H, sizeof(H), pSigData + iDBLen, iHashSize);

    /* if the leftmost 8emLen - emBits bits of the leftmost octet in maskedDB are not all equal to zero, output "inconsistent" and stop.
       note that we assume our modulus size here is a multiple of eight bits */
    if (maskedDB[0] & 0x80)
    {
        NetPrintf(("protossl: signature failed masked validity check\n"));
        return(0);
    }

    // let dbMask = MGF(H, emLen - hLen - 1).
    _Pkcs1DoMGF1(dbMask, iDBLen, H, iDBLen, eSigHash);

    // let DB = maskedDB \xor dbMask.
    for (iDB = 0; iDB < iDBLen; iDB += 1)
    {
        DB[iDB] = maskedDB[iDB] ^ dbMask[iDB];
    }

    /* set the leftmost 8emLen - emBits bits of the leftmost octet in DB to zero.
       note that we assume our modulus size here is a multiple of eight bits */
    DB[0] &= 0x7f;

    #if DEBUG_RAW_DATA
    NetPrintMem(DB, iDBLen, "certificate verify unmasked data");
    #endif

    /* if the emLen - hLen - sLen - 2 leftmost octets of DB are not zero or if the octet at position emLen - hLen - sLen - 1
       (the leftmost position is "position 1") does not have hexadecimal value 0x01, output "inconsistent" and stop. */
    for (iDB = 0, uCheck = 0; iDB < (iDBLen - iSaltSize - 1); iDB += 1)
        uCheck |= DB[iDB];
    if ((uCheck != 0) || (DB[iDB] != 1))
    {
        NetPrintf(("protossl: signature failed padding validity check\n"));
        return(0);
    }

    // validate salt length
    if (iSaltSize > (signed)sizeof(salt))
    {
        NetPrintf(("protossl: salt length %d too big in signature verify\n", iSaltSize));
        return(0);
    }
    // let salt be the last sLen octets of DB.
    ds_memcpy_s(salt, sizeof(salt), DB + iDBLen - iSaltSize, iSaltSize);

    // let M' = (0x)00 00 00 00 00 00 00 00 || mHash || salt
    ds_memclr(Mprime, 8);
    ds_memcpy(Mprime + 8, pMsgHash, iHashSize);
    ds_memcpy(Mprime + 8 + iHashSize, salt, iSaltSize);

    // let H' = Hash(M'), an octet string of length hLen.
    pHash->Init(aHashState, iHashSize);
    pHash->Update(aHashState, Mprime, 8 + iHashSize + iSaltSize);
    pHash->Final(aHashState, Hprime, iHashSize);

    #if DEBUG_RAW_DATA
    NetPrintMem(H, iHashSize, "H");
    NetPrintMem(Hprime, iHashSize, "Hprime");
    NetPrintMem(pMsgHash, iHashSize, "MsgHash");
    NetPrintMem(salt, iSaltSize, "salt");
    #endif

    // if H = H', output "consistent".  Otherwise, output "inconsistent".
    if (memcmp(H, Hprime, iHashSize))
    {
        NetPrintf(("protossl: signature failed hash validity check\n"));
        return(0);
    }

    // success
    return(1);
}

/*F********************************************************************************/
/*!
    \Function _Pkcs1EncodeEMSA_PSS

    \Description
        As per https://tools.ietf.org/html/rfc8017#section-9.1.1 encode
        specified hash data in an EMSA-PSS envelope.

    \Input *pBuffer     - [out] storage for encoded message
    \Input iBufSize     - size of output buffer
    \Input *pHashData   - message hash
    \Input iHashSize    - size of hash
    \Input eHashType    - hash type

    \Output
        int32_t         - size of encoded message, or negative on error

    \Notes
        This function deliberately uses coding standards and comments matching
        the specification to make it easier to compare while reading the specs.

    \Version 05/16/2017 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _Pkcs1EncodeEMSA_PSS(uint8_t *pBuffer, int32_t iBufSize, const uint8_t *pHashData, int32_t iHashSize, CryptHashTypeE eHashType)
{
    uint8_t maskedDB[SSL_SIG_MAX], dbMask[SSL_SIG_MAX], DB[SSL_SIG_MAX];
    uint8_t aSaltBuf[CRYPTHASH_MAXDIGEST], Mprime[(CRYPTHASH_MAXDIGEST*2)+8], H[(CRYPTHASH_MAXDIGEST*2)+8], *pTemp;
    const CryptHashT *pHash = CryptHashGet(eHashType);
    uint8_t aHashState[CRYPTHASH_MAXSTATE];
    int32_t iDB, iDBLen = iBufSize - iHashSize - 1;

    // if emLen < hLen + sLen + 2, output "encoding error" and stop.
    if (iBufSize < ((iHashSize*2)+2))
    {
        return(-1);
    }
    // generate a random octet string salt of length sLen; if sLen = 0, then salt is the empty string.
    CryptRandGet(aSaltBuf, sizeof(aSaltBuf));
    // let M' = (0x)00 00 00 00 00 00 00 00 || mHash || salt
    pTemp = Mprime;
    ds_memclr(pTemp, 8);
    pTemp += 8;
    ds_memcpy(pTemp, pHashData, iHashSize);
    pTemp += iHashSize;
    ds_memcpy(pTemp, aSaltBuf, iHashSize);

    // let H = Hash(M'), an octet string of length hLen.
    pHash->Init(aHashState, iHashSize);
    pHash->Update(aHashState, Mprime, 8 + iHashSize + iHashSize);
    pHash->Final(aHashState, H, iHashSize);

    #if DEBUG_RAW_DATA
    NetPrintMem(H, iHashSize, "H");
    NetPrintMem(pHashData, iHashSize, "MsgHash");
    NetPrintMem(aSaltBuf, iHashSize, "salt");
    #endif

    // generate an octet string PS consisting of emLen - sLen - hLen - 2 zero octets.  The length of PS may be 0.
    ds_memclr(DB, iBufSize - (iHashSize*2) - 2);
    pTemp = DB + iBufSize - (iHashSize*2) - 2;

    // let DB = PS || 0x01 || salt; DB is an octet string of length emLen - hLen - 1.
    *pTemp++ = 0x01;
    ds_memcpy(pTemp, aSaltBuf, iHashSize);

    // let dbMask = MGF(H, emLen - hLen - 1).
    _Pkcs1DoMGF1(dbMask, iDBLen, H, iDBLen, eHashType);

    // let maskedDB = DB \xor dbMask.
    for (iDB = 0; iDB < iDBLen; iDB += 1)
    {
        maskedDB[iDB] = DB[iDB] ^ dbMask[iDB];
    }

    /* set the leftmost 8emLen - emBits bits of the leftmost octet in maskedDB to zero.
       note that we assume our modulus size here is a multiple of eight bits */
    maskedDB[0] &= 0x7f;     

    // let EM = maskedDB || H || 0xbc.
    pTemp = pBuffer;
    ds_memcpy(pTemp, maskedDB, iDBLen);
    pTemp += iDBLen;
    ds_memcpy(pTemp, H, iHashSize);
    pTemp += iHashSize;
    *pTemp++ = 0xbc;

    // output EM
    return(pTemp-pBuffer);
}

/*
    session history
*/

/*F********************************************************************************/
/*!
    \Function _SessionHistorySet

    \Description
        Set entry in session history buffer

    \Input *pSecure     - secure state
    \Input *pSessHist   - session history entry to set
    \Input *pSessTick   - session ticket for session (if tls1.3; else NULL)
    \Input *pHostName   - hostname session is associated with
    \Input uPort        - port session is associated with
    \Input *pSessionId  - session id of session
    \Input *pMasterSecret- master secret associated with session
    \Input uCurTick     - current tick count

    \Version 03/15/2012 (jbrookes)
*/
/********************************************************************************F*/
static void _SessionHistorySet(SecureStateT *pSecure, SessionHistoryT *pSessHist, SessionTicketT *pSessTick, const char *pHostName, uint16_t uPort, const uint8_t *pSessionId, const uint8_t *pMasterSecret, uint32_t uCurTick)
{
    // save hostname & port
    ds_strnzcpy(pSessHist->strHost, pHostName, sizeof(pSessHist->strHost));
    pSessHist->uPort = uPort;
    // set access tick
    pSessHist->uSessionUseTick = uCurTick;
    // set expire tick; add a 250ms fudge factor to make sure we don't send an expiring ticket
    pSessHist->uSessionExpTick = (pSessTick != NULL) ? pSessTick->uLifetime * 1000 : SSL_SESSVALIDTIME_MAX;
    pSessHist->uSessionExpTick += uCurTick-250;

    if (pSessTick != NULL)
    {
        pSessHist->bSessionTicket = TRUE;
        ds_memcpy_s(&pSessHist->SessionTicket, sizeof(pSessHist->SessionTicket), pSessTick, sizeof(*pSessTick));
    }
    else
    {
        SessionInfoT *pSessInfo = &pSessHist->SessionInfo;
        pSessHist->bSessionTicket = FALSE;
        pSessInfo->uSslVersion = pSecure->uSslVersion;
        pSessInfo->uCipherId = pSecure->pCipher->uIdent;
        ds_memcpy(pSessInfo->SessionId, pSessionId, sizeof(pSessInfo->SessionId));
        ds_memcpy(pSessInfo->MasterSecret, pMasterSecret, sizeof(pSessInfo->MasterSecret));
    }
}

/*F********************************************************************************/
/*!
    \Function _SessionHistoryGet

    \Description
        Returns pointer to specified session history entry, or NULL if not found.

    \Input *pHostName   - hostname associated with session (may be null)
    \Input uPort        - port associated with session (if using hostname)
    \Input *pSessionId  - session id to look for (may be null)

    \Output
        SessionHistoryT * - session history entry, or NULL if not found

    \Version 03/15/2012 (jbrookes)
*/
/********************************************************************************F*/
static SessionHistoryT *_SessionHistoryGet(const char *pHostName, uint16_t uPort, const uint8_t *pSessionId)
{
    SessionHistoryT *pSessHist, *pSessInfo;
    ProtoSSLStateT *pState = _ProtoSSL_pState;
    const uint8_t aZeroSessionId[32] = { 0 };
    int32_t iSess;

    #if DIRTYCODE_DEBUG
    if (pState == NULL)
    {
        NetPrintf(("protossl: warning, protossl not initialized, session history feature is disabled\n"));
        return(NULL);
    }
    #endif

    // exclude NULL sessionId
    if ((pSessionId != NULL) && !memcmp(pSessionId, aZeroSessionId, sizeof(aZeroSessionId)))
    {
        return(NULL);
    }

    // search session history for a match
    for (iSess = 0, pSessInfo = NULL; (iSess < SSL_SESSHIST_MAX) && (pSessInfo == NULL); iSess += 1)
    {
        pSessHist = &pState->SessionHistory[iSess];

        // make sure the ticket isn't expired
        if (NetTickDiff(NetTick(), pSessHist->uSessionExpTick) > 0)
        {
            NetPrintfVerbose((DEBUG_RES_SESS, 0, "protossl: expiring session for %s:%d\n", pSessHist->strHost, pSessHist->uPort));
            continue;
        }

        // check for resume by hostname/port
        if ((pHostName != NULL) && (!strcmp(pSessHist->strHost, pHostName)) && (pSessHist->uPort == uPort))
        {
            pSessInfo = pSessHist;
        }
        // check for resume by sessionId (tls1.2 and prior)
        if ((pSessionId != NULL) && (!memcmp(pSessHist->SessionInfo.SessionId, pSessionId, sizeof(pSessHist->SessionInfo.SessionId))))
        {
            pSessInfo = pSessHist;
        }
    }

    // return session (or null) to caller
    return(pSessInfo);
}

/*F********************************************************************************/
/*!
    \Function _SessionHistoryAdd

    \Description
        Save a new session in the session history buffer

    \Input *pSecure     - secure state
    \Input *pHostName   - hostname session is for
    \Input uPort        - port session is for
    \Input *pSessTick   - session ticket for session, if tls1.3
    \Input *pSessionId  - session id for session
    \Input iSessionLen  - length of session
    \Input *pMasterSecret - master secret associated with session

    \Version 03/15/2012 (jbrookes)
*/
/********************************************************************************F*/
static void _SessionHistoryAdd(SecureStateT *pSecure, const char *pHostName, uint16_t uPort, SessionTicketT *pSessTick, const uint8_t *pSessionId, uint32_t iSessionLen, const uint8_t *pMasterSecret)
{
    ProtoSSLStateT *pState = _ProtoSSL_pState;
    SessionHistoryT *pSessHist;
    int32_t iSess, iSessOldest;
    int32_t iTickDiff, iTickDiffMax;
    uint32_t uCurTick = NetTick();

    #if DIRTYCODE_DEBUG
    if (pState == NULL)
    {
        NetPrintf(("protossl: warning, protossl not initialized, session history feature is disabled\n"));
        return;
    }
    #endif

    NetCritEnter(&pState->StateCrit);

    // see if we already have a session for this peer
    for (iSess = 0; iSess < SSL_SESSHIST_MAX; iSess += 1)
    {
        pSessHist = &pState->SessionHistory[iSess];
        // already have this peer in our history?
        if (!strcmp(pSessHist->strHost, pHostName) && (pSessHist->uPort == uPort))
        {
            NetPrintfVerbose((DEBUG_RES_SESS, 0, "protossl: updating session history entry %d (host=%s:%d)\n", iSess, pHostName, uPort));
            _SessionHistorySet(pSecure, pSessHist, pSessTick, pHostName, uPort, pSessionId, pMasterSecret, uCurTick);
            NetCritLeave(&pState->StateCrit);
            return;
        }
    }

    // try to find an unallocated session
    for (iSess = 0; iSess < SSL_SESSHIST_MAX; iSess += 1)
    {
        pSessHist = &pState->SessionHistory[iSess];
        // empty slot?
        if (pSessHist->strHost[0] == '\0')
        {
            NetPrintfVerbose((DEBUG_RES_SESS, 0, "protossl: adding session history entry %d (host=%s:%d)\n", iSess, pHostName, uPort));
            _SessionHistorySet(pSecure, pSessHist, pSessTick, pHostName, uPort, pSessionId, pMasterSecret, uCurTick);
            NetCritLeave(&pState->StateCrit);
            return;
        }
    }

    // find the oldest session
    for (iSess = 0, iTickDiffMax = 0, iSessOldest = 0; iSess < SSL_SESSHIST_MAX; iSess += 1)
    {
        pSessHist = &pState->SessionHistory[iSess];
        // find least recently updated session
        if ((iTickDiff = NetTickDiff(uCurTick, pSessHist->uSessionUseTick)) > iTickDiffMax)
        {
            iTickDiffMax = iTickDiff;
            iSessOldest = iSess;
        }
    }

    NetPrintfVerbose((DEBUG_RES_SESS, 0, "protossl: replacing session history entry %d (host=%s:%d)\n", iSessOldest, pHostName, uPort));
    _SessionHistorySet(pSecure, &pState->SessionHistory[iSessOldest], pSessTick, pHostName, uPort, pSessionId, pMasterSecret, uCurTick);

    NetCritLeave(&pState->StateCrit);
}

/*F********************************************************************************/
/*!
    \Function _SessionHistoryInvalidate

    \Description
        Invalidate specified session from session history cache

    \Input *pHostName   - hostname of session to invalidate
    \Input uPort        - port of session to invalidate

    \Version 02/04/2014 (jbrookes)
*/
/********************************************************************************F*/
static void _SessionHistoryInvalidate(const char *pHostName, uint16_t uPort)
{
    SessionHistoryT *pSessInfo = NULL;
    ProtoSSLStateT *pState = _ProtoSSL_pState;

    #if DIRTYCODE_DEBUG
    if (pState == NULL)
    {
        NetPrintf(("protossl: warning, protossl not initialized, session history feature is disabled\n"));
        return;
    }
    #endif

    // acquire access to session history
    NetCritEnter(&pState->StateCrit);

    // find session history
    if ((pSessInfo = _SessionHistoryGet(pHostName, uPort, NULL)) != NULL)
    {
        // reset entry
        NetPrintf(("protossl: invalidating session entry\n"));
        ds_memclr(pSessInfo, sizeof(*pSessInfo));
    }

    // release access to session history, return to caller
    NetCritLeave(&pState->StateCrit);
}

/*F********************************************************************************/
/*!
    \Function _SessionHistoryGetInfo

    \Description
        Check to see if we can find a session for the specified address or session
        id in our history buffer, and return sesession info if found.

    \Input *pSessBuff   - [out] storage for session history entry
    \Input *pHostName   - hostname of session (may be null)
    \Input uPort        - port of session (if hostname is not null)
    \Input *pSessionId  - session id to look for (may be null)

    \Output
        SessionHistoryT * - session history entry, or NULL if not found

    \Version 03/15/2012 (jbrookes)
*/
/********************************************************************************F*/
static SessionHistoryT *_SessionHistoryGetInfo(SessionHistoryT *pSessBuff, const char *pHostName, uint16_t uPort, const uint8_t *pSessionId)
{
    SessionHistoryT *pSessInfo = NULL;
    ProtoSSLStateT *pState = _ProtoSSL_pState;

    #if DIRTYCODE_DEBUG
    if (pState == NULL)
    {
        NetPrintf(("protossl: warning, protossl not initialized, session history feature is disabled\n"));
        return(NULL);
    }
    #endif

    // acquire access to session history
    NetCritEnter(&pState->StateCrit);

    // find session history
    if ((pSessInfo = _SessionHistoryGet(pHostName, uPort, pSessionId)) != NULL)
    {
        // update timestamp
        pSessInfo->uSessionUseTick = NetTick();
        // copy to caller
        ds_memcpy(pSessBuff, pSessInfo, sizeof(*pSessBuff));
        pSessInfo = pSessBuff;
    }

    // release access to session history, return to caller
    NetCritLeave(&pState->StateCrit);
    return(pSessInfo);
}

/*
    certificate validation vistory
*/

/*F********************************************************************************/
/*!
    \Function _CertValidHistorySet

    \Description
        Set entry in certvalid history buffer

    \Input *pCertHist   - session history entry to set
    \Input *pFingerprintId - fingerprint for certificates validated
    \Input uCertSize    - certificate size
    \Input uCurTick     - current tick count

    \Version 04/29/2014 (jbrookes)
*/
/********************************************************************************F*/
static void _CertValidHistorySet(CertValidHistoryT *pCertHist, const uint8_t *pFingerprintId, uint32_t uCertSize, uint32_t uCurTick)
{
    // update fingerprint
    ds_memcpy(pCertHist->FingerprintId, pFingerprintId, sizeof(pCertHist->FingerprintId));
    // save cert size
    pCertHist->uCertSize = uCertSize;
    // set timestamp
    pCertHist->uCertValidTick = uCurTick;
}

/*F********************************************************************************/
/*!
    \Function _CertValidHistoryGet

    \Description
        Returns pointer to specified session history entry, or NULL if not found.

    \Input *pFingerprintId - certificate fingerprint
    \Input uCertSize    - size of certificate to check in validity history

    \Output
        CertValidHistoryT * - certificate validation history entry, or NULL if not found

    \Version 04/29/2014 (jbrookes)
*/
/********************************************************************************F*/
static CertValidHistoryT *_CertValidHistoryGet(const uint8_t *pFingerprintId, uint32_t uCertSize)
{
    CertValidHistoryT *pCertHist, *pCertInfo;
    ProtoSSLStateT *pState = _ProtoSSL_pState;
    const uint8_t aZeroFingerprintId[SSL_FINGERPRINT_SIZE] = { 0 };
    uint32_t uCurTick = NetTick();
    int32_t iCert;

    #if DIRTYCODE_DEBUG
    if (pState == NULL)
    {
        NetPrintf(("protossl: warning, protossl not initialized, certificate validation history feature is disabled\n"));
        return(NULL);
    }
    #endif

    // exclude NULL fingerprintId request
    if ((pFingerprintId == NULL) || !memcmp(pFingerprintId, aZeroFingerprintId, sizeof(aZeroFingerprintId)))
    {
        return(NULL);
    }

    // find certificate history
    for (iCert = 0, pCertInfo = NULL; iCert < SSL_CERTVALID_MAX; iCert += 1)
    {
        // ref history entry
        pCertHist = &pState->CertValidHistory[iCert];
        // skip null entries
        if (pCertHist->uCertSize == 0)
        {
            continue;
        }
        // check for expiration
        if (NetTickDiff(uCurTick, pCertHist->uCertValidTick) > SSL_CERTVALIDTIME_MAX)
        {
            NetPrintfVerbose((DEBUG_VAL_CERT, 0, "protossl: expiring certificate validity for entry %d\n", iCert));
            ds_memclr(pCertHist, sizeof(*pCertHist));
            continue;
        }
        // check for match
        if ((pCertHist->uCertSize == uCertSize) && (!memcmp(pCertHist->FingerprintId, pFingerprintId, sizeof(pCertHist->FingerprintId))))
        {
            pCertInfo = pCertHist;
            break;
        }
    }

    // return session (or null) to caller
    return(pCertInfo);
}

/*F********************************************************************************/
/*!
    \Function _CertValidHistoryAdd

    \Description
        Added fingerprint of validated certificate to certificate validity history buffer

    \Input *pFingerprintId  - certificate fingerprint
    \Input uCertSize        - size of certificate

    \Version 03/15/2012 (jbrookes)
*/
/********************************************************************************F*/
static void _CertValidHistoryAdd(const uint8_t *pFingerprintId, uint32_t uCertSize)
{
    ProtoSSLStateT *pState = _ProtoSSL_pState;
    CertValidHistoryT *pCertHist;
    int32_t iCert, iCertOldest;
    int32_t iTickDiff, iTickDiffMax;
    uint32_t uCurTick = NetTick();

    #if DIRTYCODE_DEBUG
    if (pState == NULL)
    {
        NetPrintf(("protossl: warning, protossl not initialized, certificate validation history feature is disabled\n"));
        return;
    }
    #endif

    NetCritEnter(&pState->StateCrit);

    // try to find an unallocated session
    for (iCert = 0; iCert < SSL_CERTVALID_MAX; iCert += 1)
    {
        pCertHist = &pState->CertValidHistory[iCert];
        // empty slot?
        if (pCertHist->uCertSize == 0)
        {
            #if DEBUG_VAL_CERT
            NetPrintf(("protossl: adding certificate validation history entry %d\n", iCert));
            #endif
            _CertValidHistorySet(pCertHist, pFingerprintId, uCertSize, uCurTick);
            NetCritLeave(&pState->StateCrit);
            return;
        }
    }

    // find the oldest session
    for (iCert = 0, iTickDiffMax = 0, iCertOldest = 0; iCert < SSL_CERTVALID_MAX; iCert += 1)
    {
        pCertHist = &pState->CertValidHistory[iCert];
        // find least recently updated session
        if ((iTickDiff = NetTickDiff(uCurTick, pCertHist->uCertValidTick)) > iTickDiffMax)
        {
            iTickDiffMax = iTickDiff;
            iCertOldest = iCert;
        }
    }

    NetPrintfVerbose((DEBUG_VAL_CERT, 0, "protossl: replacing certificate validation entry %d\n", iCertOldest));
    _CertValidHistorySet(&pState->CertValidHistory[iCertOldest], pFingerprintId, uCertSize, uCurTick);

    NetCritLeave(&pState->StateCrit);
}

/*F********************************************************************************/
/*!
    \Function _CertValidHistoryCheck

    \Description
        Check to see if certificate fingerprint is in certificiate validity history

    \Input *pFingerprintId  - certificate fingerprint
    \Input uCertSize        - size of certificate

    \Output
        int32_t             - zero=not validated, else validated

    \Version 04/29/2014 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _CertValidHistoryCheck(const uint8_t *pFingerprintId, uint32_t uCertSize)
{
    ProtoSSLStateT *pState = _ProtoSSL_pState;
    CertValidHistoryT *pCertInfo;
    int32_t iResult = 0;

    #if DIRTYCODE_DEBUG
    if (pState == NULL)
    {
        NetPrintf(("protossl: warning, protossl not initialized, certificate validation history feature is disabled\n"));
        return(0);
    }
    #endif

    // acquire access to session history
    NetCritEnter(&pState->StateCrit);

    // find certificate validity history
    if ((pCertInfo = _CertValidHistoryGet(pFingerprintId, uCertSize)) != NULL)
    {
        iResult = 1;
        NetPrintfVerbose((DEBUG_VAL_CERT, 0, "protossl: certificate validation deferred to cache\n"));
    }

    // release access to session history, return to caller
    NetCritLeave(&pState->StateCrit);
    return(iResult);
}

/*
    digital signature generation and verification
*/

/*F********************************************************************************/
/*!
    \Function _ProtoSSLEccInitContext

    \Description
        Initialize ECC context for key exchange and retrieve the dh interface

    \Input *pSecure             - secure state
    \Input *pEllipticCurve      - curve to init state with

    \Output
        const CryptCurveDhT *   - the found dh interface or NULL if not found for curve

    \Version 03/14/2018 (jbrookes)
*/
/********************************************************************************F*/
static const CryptCurveDhT *_ProtoSSLEccInitContext(SecureStateT *pSecure, const EllipticCurveT *pEllipticCurve)
{
    const CryptCurveDhT *pEcc;

    // find the dh interface, this _should not_ fail if everything is correctly configured
    if ((pEcc = CryptCurveGetDh(pEllipticCurve->uIdent)) == NULL)
    {
        NetPrintf(("protossl: could not find curve %s (ident=0x%04x), mismatch between protossl and cryptcurve?\n",
            pEllipticCurve->strName, pEllipticCurve->uIdent));
        return(NULL);
    }
    if (pSecure->bEccContextInitialized)
    {
        return(pEcc);
    }
    // init the state
    if (pEcc->Init(pSecure->EccContext, pEllipticCurve->uIdent) == 0)
    {
        pSecure->bEccContextInitialized = TRUE;
    }
    else
    {
        NetPrintf(("protossl: initialize failed for curve %s (ident=0x%04x)\n", pEllipticCurve->strName, pEllipticCurve->uIdent));
    }
    return(pEcc);
}

/*F********************************************************************************/
/*
    \Function _ProtoSSLDsaInitContext

    \Description
        Gets the interface for the ecdsa curve that we use for signing
        and verifying

    \Input *pSigVerify          - the signature verify state
    \Input iCrvType             - the asn identifier for the ecdsa curve

    \Output
        const CryptCurveDsaT *  - the interface or NULL if not found

    \Version 05/10/2018 (eesponda)
*/
/********************************************************************************F*/
static const CryptCurveDsaT *_ProtoSSLDsaInitContext(SignatureVerifyT *pSigVerify, int32_t iCrvType)
{
    const CryptCurveDsaT *pDsa;
    int32_t iCurveIdent = 0;

    // map the ASN to the curve we want to use
    if (iCrvType == ASN_OBJ_ECDSA_SECP256R1)
    {
        iCurveIdent = CRYPTCURVE_SECP256R1;
    }
    else if (iCrvType == ASN_OBJ_ECDSA_SECP384R1)
    {
        iCurveIdent = CRYPTCURVE_SECP384R1;
    }

    // find the dsa interface, this _should not_ fail if everything is correctly configured
    if ((pDsa = CryptCurveGetDsa(iCurveIdent)) == NULL)
    {
        NetPrintf(("protossl: could not find curve ident 0x%04x for dsa, mismatch between protossl and cryptcurve?\n",
            iCurveIdent));
        return(NULL);
    }
    if (pSigVerify->bEccContextInitialized)
    {
        return(pDsa);
    }

    // attempt to initialize, catch failure in case we get an unsupported curve
    if (pDsa->Init(pSigVerify->DsaContext, iCurveIdent) == 0)
    {
        pSigVerify->bEccContextInitialized = TRUE;
    }
    else
    {
        NetPrintf(("protossl: initialize failed for dsa curve (ident=0x%04x)\n", iCurveIdent));
    }
    return(pDsa);
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLGenerateSignatureHash

    \Description
        Generate a signature for key exchange

    \Input *pSecure     - secure state
    \Input *pHash       - signature hash algorithm
    \Input *pHashDigest - [out] buffer for signature hash
    \Input *pData       - data to sign
    \Input iDataLen     - length of data

    \Output
        int32_t         - size of signature hash

    \Version 02/17/2018 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _ProtoSSLGenerateSignatureHash(const SecureStateT *pSecure, const CryptHashT *pHash, uint8_t *pHashDigest, const uint8_t *pData, int32_t iDataLen)
{
    uint8_t HashState[CRYPTHASH_MAXSTATE];
    pHash->Init(HashState, pHash->iHashSize);
    pHash->Update(HashState, pSecure->ClientRandom, sizeof(pSecure->ClientRandom));
    pHash->Update(HashState, pSecure->ServerRandom, sizeof(pSecure->ServerRandom));
    pHash->Update(HashState, pData, iDataLen);
    pHash->Final(HashState, pHashDigest, pHash->iHashSize);
    return(pHash->iHashSize);
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLGenerateSignatureEcdsa

    \Description
        Generate an ECDSA signature; expected to be called iteratively

    \Input *pSecure     - secure state
    \Input *pSigData    - [out] signature data buffer
    \Input iSigSize     - signature data size
    \Input *pPrivateKey - ECC private key data
    \Input iKeySize     - size of key
    \Input iCrvType     - curve type

    \Output
        int32_t         - one=call again, zero=success, negative=failure

    \Version 03/09/2018 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _ProtoSSLGenerateSignatureEcdsa(SecureStateT *pSecure, const uint8_t *pSigData, int32_t iSigSize, const uint8_t *pPrivateKey, int32_t iKeySize, int32_t iCrvType)
{
    CryptEccPointT Signature;
    int32_t iResult;
    CryptBnT PrivKey;
    uint32_t uCryptUsecs;
    SignatureVerifyT *pSigVerify = &pSecure->SigVerify;
    const CryptCurveDsaT *pDsa;

    // get the dsa functions
    if ((pDsa = _ProtoSSLDsaInitContext(pSigVerify, iCrvType)) == NULL)
    {
        return(-1);
    }
    // if the state is not initialized then assume a failure
    if (!pSigVerify->bEccContextInitialized)
    {
        return(-1);
    }

    // init private key
    CryptBnInitFrom(&PrivKey, -1, pPrivateKey, iKeySize);

    // sign digest with the private key
    if ((iResult = pDsa->Sign(pSigVerify->DsaContext, &PrivKey, pSigData, iSigSize, &Signature, &uCryptUsecs)) != 1)
    {
        NetPrintfVerbose((DEBUG_ENC_PERF, 0, "protossl: SSL Perf (ecdsa sig encrypt) %dms\n", uCryptUsecs/1000));
        pSecure->uTimer += uCryptUsecs/1000;
        pSigVerify->iSigSize = _AsnWriteSignatureEcdsa(pSigVerify->aSigData, sizeof(pSigVerify->aSigData), &Signature);
        pSigVerify->bEccContextInitialized = FALSE;
    }
    return(iResult);
}
/*F********************************************************************************/
/*!
    \Function _ProtoSSLGenerateSignatureRSA

    \Description
        Generate an RSA signature; expected to be called iteratively

    \Input *pSecure     - secure state
    \Input *pSigData    - signature data
    \Input iSigSize     - signature data size
    \Input *pPrivateKey - private key used to sign

    \Output
        int32_t         - one=call again, zero=success, negative=failure

    \Version 03/09/2018 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _ProtoSSLGenerateSignatureRSA(SecureStateT *pSecure, const uint8_t *pSigData, int32_t iSigSize, const X509PrivateKeyT *pPrivateKey)
{
    // set up for rsa encryption
    if (!pSecure->bRSAContextInitialized)
    {
        // setup to encrypt signature object using private key
        if (CryptRSAInit2(&pSecure->RSAContext, pPrivateKey->Modulus.iObjSize, &pPrivateKey->PrimeP, &pPrivateKey->PrimeQ, &pPrivateKey->ExponentP, &pPrivateKey->ExponentQ, &pPrivateKey->Coefficient) != 0)
        {
            NetPrintf(("protossl: RSA init failed on private key encrypt\n"));
            return(-1);
        }
        // encrypt signature object
        if ((pSecure->uSslVersion < SSL3_TLS1_2) || (pSecure->pSigScheme->uVerifyScheme == SSL3_SIGVERIFY_RSA_PKCS1))
        {
            CryptRSAInitPrivate(&pSecure->RSAContext, pSigData, iSigSize);
        }
        else
        {
            CryptRSAInitSignature(&pSecure->RSAContext, pSigData, iSigSize);
        }
        pSecure->bRSAContextInitialized = TRUE;
    }

    /* encrypt signature hash; break it up into chunks of 64 to prevent blocking thread entire time.
       we don't do them one at a time because for a typical private key that would be 2048 calls and
       would incur unacceptable overhead, so this is a compromise between doing it all at once and
       doing it one step at a time */
    if (CryptRSAEncrypt(&pSecure->RSAContext, 64) > 0)
    {
        return(1);
    }

    // copy data to signature output
    pSecure->SigVerify.iSigSize = pSecure->RSAContext.iKeyModSize;
    ds_memcpy_s(pSecure->SigVerify.aSigData, sizeof(pSecure->SigVerify.aSigData), pSecure->RSAContext.EncryptBlock, pSecure->SigVerify.iSigSize);

    // done
    NetPrintfVerbose((DEBUG_ENC_PERF, 0, "protossl: SSL Perf (rsa sig encrypt) %dms (%d calls)\n",
        pSecure->RSAContext.uCryptMsecs, pSecure->RSAContext.uNumExpCalls));
    pSecure->uTimer += pSecure->RSAContext.uCryptMsecs;
    pSecure->bRSAContextInitialized = FALSE;
    return(0);
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLGenerateSignatureAsync

    \Description
        Generate a digital signature; expected to be called iteratively
        by the async update function.

    \Input *pState      - module state

    \Output
        int32_t         - one=call again, zero=success, negative=failure

    \Version 03/09/2018 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _ProtoSSLGenerateSignatureAsync(ProtoSSLRefT *pState)
{
    SecureStateT *pSecure = pState->pSecure;
    SignatureVerifyT *pSigVerify = &pSecure->SigVerify;
    int32_t iResult;

    if ((pSigVerify->pSigScheme != NULL) && (pSigVerify->pSigScheme->SigAlg.uSigAlg == SSL3_SIGALG_ECDSA))
    {
        iResult = _ProtoSSLGenerateSignatureEcdsa(pSecure, pSigVerify->aSigData, pSigVerify->iSigSize, pSigVerify->aKeyData, pSigVerify->iKeySize, (pSigVerify->iKeySize == 32) ? ASN_OBJ_ECDSA_SECP256R1 : ASN_OBJ_ECDSA_SECP384R1);
    }
    else
    {
        iResult = _ProtoSSLGenerateSignatureRSA(pSecure, pSigVerify->aSigData, pSigVerify->iSigSize, pSigVerify->pPrivateKey);
    }
    if (iResult <= 0)
    {
        if (iResult < 0)
        {
            NetPrintf(("protossl: %s signature generate failed\n", pSigVerify->pSigScheme->SigAlg.uSigAlg == SSL3_SIGALG_ECDSA ? "ecdsa" : "rsa"));
        }
        pSigVerify->pSigScheme = NULL;
        pSecure->bSigGenerated = TRUE;
    }
    return(iResult);
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLGenerateSignature

    \Description
        Starts async operation to generate a digital signature

    \Input *pState      - module state
    \Input *pSecure     - secure state
    \Input *pSigScheme  - signature scheme to use
    \Input *pSigData    - signature to sign
    \Input iSigSize     - signature size
    \Input *pPrivateKey - private key
    \Input iNextState   - next state to transition to after async op

    \Output
        int32_t         - one=call again, zero=success, negative=failure

    \Version 03/09/2018 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _ProtoSSLGenerateSignature(ProtoSSLRefT *pState, SecureStateT *pSecure, const SignatureSchemeT *pSigScheme, const uint8_t *pSigData, int32_t iSigSize, X509PrivateKeyT *pPrivateKey, int32_t iNextState)
{
    SignatureVerifyT *pSigVerify = &pSecure->SigVerify;
    
    pSigVerify->pSigScheme = pSigScheme;
    ds_memcpy_s(pSigVerify->aSigData, sizeof(pSigVerify->aSigData), pSigData, iSigSize);
    pSigVerify->iSigSize = iSigSize;
    pSigVerify->pPrivateKey = pPrivateKey;
    pSigVerify->iNextState = iNextState;
    pSigVerify->bEccContextInitialized = FALSE;
    if (pSigScheme->SigAlg.uSigAlg == SSL3_SIGALG_ECDSA)
    {
        pSigVerify->iKeySize = pPrivateKey->Modulus.iObjSize;
        ds_memcpy_s(pSigVerify->aKeyData, sizeof(pSigVerify->aKeyData), pPrivateKey->Modulus.pObjData, pSigVerify->iKeySize);
    }

    return(_ProtoSSLUpdateSetAsyncState(pState, _ProtoSSLGenerateSignatureAsync, iNextState, ST_FAIL_SETUP, SSL3_ALERT_DESC_INTERNAL_ERROR));
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLVerifySignatureEcdsa

    \Description
        Verify an ECDSA signature; expected to be called iteratively

    \Input *pSecure     - secure state
    \Input *pSigData    - signature data
    \Input iSigSize     - signature data size
    \Input *pHashData   - hash to validate
    \Input iHashSize    - hash size
    \Input *pPubKey     - ECC public key data
    \Input iPubKeySize  - size of key
    \Input iCrvType     - curve type

    \Output
        int32_t         - one=call again, zero=success, negative=failure

    \Version 02/09/2018 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _ProtoSSLVerifySignatureEcdsa(SecureStateT *pSecure, const uint8_t *pSigData, int32_t iSigSize, const uint8_t *pHashData, int32_t iHashSize, const uint8_t *pPubKey, int32_t iPubKeySize, int32_t iCrvType)
{
    CryptEccPointT PubKey, Signature;
    const uint8_t *pData = pSigData, *pLast = pSigData+iSigSize;
    SignatureVerifyT *pSigVerify = &pSecure->SigVerify;
    const CryptCurveDsaT *pDsa;
    uint8_t aSigData[128];
    int32_t iSize, iResult;
    uint32_t uCryptUsecs;

    // get the dsa functions
    if ((pDsa = _ProtoSSLDsaInitContext(pSigVerify, iCrvType)) == NULL)
    {
        return(-1);
    }
    // if the state is not initialized then assume a failure
    if (!pSigVerify->bEccContextInitialized)
    {
        return(-1);
    }

    // init public key
    pDsa->PointInit(&PubKey, pPubKey, iPubKeySize);

    // parse the sequence
    if ((pData = _AsnParseHeaderType(pData, pLast, ASN_TYPE_SEQN+ASN_CONSTRUCT, &iSize)) == NULL)
    {
        return(-1);
    }
    // parse and set the first component
    if ((pData = _AsnParseBinary(pData, pLast, ASN_TYPE_INTEGER+ASN_PRIMITIVE, aSigData, sizeof(aSigData), &iSize, "signature data x")) == NULL)
    {
        return(-1);
    }
    CryptBnInitFrom(&Signature.X, -1, aSigData, iSize);
    // parse and set the second component
    if ((pData = _AsnParseBinary(pData, pLast, ASN_TYPE_INTEGER+ASN_PRIMITIVE, aSigData, sizeof(aSigData), &iSize, "signature data y")) == NULL)
    {
        return(-1);
    }
    CryptBnInitFrom(&Signature.Y, -1, aSigData, iSize);

    // do the verify
    if ((iResult = pDsa->Verify(pSigVerify->DsaContext, &PubKey, pHashData, iHashSize, &Signature, &uCryptUsecs)) != 1)
    {
        NetPrintfVerbose((DEBUG_ENC_PERF, 0, "protossl: SSL Perf (ecdsa sig verify) %dms\n", uCryptUsecs/1000));
        pSecure->uTimer += uCryptUsecs/1000;
        pSigVerify->bEccContextInitialized = FALSE;
    }
    return(iResult);
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLVerifySignatureRSA

    \Description
        Verify an RSA signature; expected to be called iteratively

    \Input *pSecure     - secure state
    \Input *pSigData    - signature data
    \Input iSigSize     - signature data size
    \Input iSigType     - signature type
    \Input *pHashData   - hash to validate
    \Input iHashSize    - hash size
    \Input eHashType    - hash type
    \Input iSigSalt     - signature salt length
    \Input eMgfHash     - signature message generation function (mgf)
    \Input *pKeyModData - rsa public key modulus
    \Input iKeyModSize  - key modulus length
    \Input *pKeyExpData - rsa public key exponent
    \Input iKeyExpSize  - key exponent length

    \Output
        int32_t         - one=call again, zero=success, negative=failure

    \Version 07/22/2009 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _ProtoSSLVerifySignatureRSA(SecureStateT *pSecure, const uint8_t *pSigData, int32_t iSigSize, int32_t iSigType, const uint8_t *pHashData, int32_t iHashSize, CryptHashTypeE eHashType, int32_t iSigSalt, CryptHashTypeE eMgfHash, const uint8_t *pKeyModData, int32_t iKeyModSize, const uint8_t *pKeyExpData, int32_t iKeyExpSize)
{
    int32_t iResult;

    // set up for rsa encryption
    if (!pSecure->bRSAContextInitialized)
    {
        if (CryptRSAInit(&pSecure->RSAContext, pKeyModData, iKeyModSize, pKeyExpData, iKeyExpSize))
        {
            return(-1);
        }
        CryptRSAInitSignature(&pSecure->RSAContext, pSigData, iSigSize);
        pSecure->bRSAContextInitialized = TRUE;
    }
    // do a round of encryption
    if ((iResult = CryptRSAEncrypt(&pSecure->RSAContext, 16)) > 0)
    {
        return(iResult);
    }
    pSecure->bRSAContextInitialized = FALSE;
    NetPrintfVerbose((DEBUG_ENC_PERF, 0, "protossl: SSL Perf (rsa sig verify) %dms\n", pSecure->RSAContext.uCryptMsecs));
    pSecure->uTimer += pSecure->RSAContext.uCryptMsecs;

    #if DEBUG_RAW_DATA
    NetPrintMem(pSecure->RSAContext.EncryptBlock, pSecure->RSAContext.iKeyModSize, "decrypted signature");
    #endif

    // validate decrypted signature
    if (iSigType != ASN_OBJ_RSASSA_PSS)
    {
        iResult = -1;
        // extract hash data from signature block
        if ((pSigData = _Pkcs1VerifyEMSA(pSecure->RSAContext.EncryptBlock, iSigSize, iHashSize, eHashType)) != NULL)
        {
            // compare hash data with precalculated certificate body hash
            iResult = !memcmp(pHashData, pSigData, iHashSize) ? 0 : -1;
        }
    }
    else
    {
        // extract and validate signature hash
        iResult = _Pkcs1VerifyEMSA_PSS(pSecure->RSAContext.EncryptBlock, iKeyModSize, eHashType, iSigSalt, pHashData, iHashSize, eMgfHash) ? 0 : -1;
    }

    // return result to caller
    return(iResult);
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLVerifySignatureAsync

    \Description
        Verify a digital signature; expected to be called iteratively
        by the async update function.

    \Input *pState      - module state

    \Output
        int32_t         - one=call again, zero=success, negative=failure

    \Version 02/16/2018 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _ProtoSSLVerifySignatureAsync(ProtoSSLRefT *pState)
{
    SecureStateT *pSecure = pState->pSecure;
    SignatureVerifyT *pSigVerify = &pSecure->SigVerify;
    int32_t iResult;

    if ((pSigVerify->pSigScheme != NULL) && (pSigVerify->pSigScheme->SigAlg.uSigAlg == SSL3_SIGALG_ECDSA))
    {
        iResult = _ProtoSSLVerifySignatureEcdsa(pSecure, pSigVerify->aSigData, pSigVerify->iSigSize, pSigVerify->aHashDigest, pSigVerify->iHashSize, pSecure->Cert.KeyModData, pSecure->Cert.iKeyModSize, pSecure->Cert.iCrvType);
    }
    else
    {
        iResult = _ProtoSSLVerifySignatureRSA(pSecure, pSigVerify->aSigData, pSigVerify->iSigSize, (pSigVerify->pSigScheme->uVerifyScheme == SSL3_SIGVERIFY_RSA_PSS) ? ASN_OBJ_RSASSA_PSS : ASN_OBJ_RSA_PKCS_SHA256,
            pSigVerify->aHashDigest, pSigVerify->iHashSize, pSigVerify->eHashType, pSigVerify->iHashSize, pSigVerify->eHashType, pSecure->Cert.KeyModData, pSecure->Cert.iKeyModSize, pSecure->Cert.KeyExpData,
            pSecure->Cert.iKeyExpSize);
    }
    if (iResult <= 0)
    {
        if (iResult < 0)
        {
            NetPrintf(("protossl: %s signature verify failed\n", pSigVerify->pSigScheme->SigAlg.uSigAlg == SSL3_SIGALG_ECDSA ? "ecdsa" : "rsa"));
        }
        pSigVerify->pSigScheme = NULL;
    }
    return(iResult);
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLVerifySignature

    \Description
        Starts async operation to verify a digital signature

    \Input *pState      - module state
    \Input *pSecure     - secure state
    \Input *pSigScheme  - signature scheme to use
    \Input *pSigData    - signature data
    \Input iSigSize     - signature data size
    \Input *pHashData   - hash to validate
    \Input iHashSize    - hash size
    \Input eHashType    - hash type
    \Input iNextState   - next state to transition to after async op

    \Output
        int32_t         - one=call again, zero=success, negative=failure

    \Version 02/16/2018 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _ProtoSSLVerifySignature(ProtoSSLRefT *pState, SecureStateT *pSecure, const SignatureSchemeT *pSigScheme, const uint8_t *pSigData, int32_t iSigSize, const uint8_t *pHashData, int32_t iHashSize, CryptHashTypeE eHashType, int32_t iNextState)
{
    SignatureVerifyT *pSigVerify = &pSecure->SigVerify;

    pSigVerify->pSigScheme = pSigScheme;
    ds_memcpy_s(pSigVerify->aSigData, sizeof(pSigVerify->aSigData), pSigData, iSigSize);
    pSigVerify->iSigSize = iSigSize;
    ds_memcpy_s(pSigVerify->aHashDigest, sizeof(pSigVerify->aHashDigest), pHashData, iHashSize);
    pSigVerify->iHashSize = iHashSize;
    pSigVerify->eHashType = eHashType;
    pSigVerify->iNextState = iNextState;
    pSigVerify->bEccContextInitialized = FALSE;

    return(_ProtoSSLUpdateSetAsyncState(pState, _ProtoSSLVerifySignatureAsync, iNextState, ST_FAIL_SETUP, SSL3_ALERT_DESC_DECRYPT_ERROR));
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLVerifyCertificateSignature

    \Description
        Check an X.509 signature for validity; BLOCKING

    \Input *pSecure     - secure state
    \Input *pCert       - pointer to certificate to validate
    \Input *pKeyModData - pointer to key modulus data
    \Input iKeyModSize  - size of key modulus data
    \Input *pKeyExpData - pointer to key exponent data
    \Input iKeyExpSize  - size of key exponent data
    \Input iKeyType     - key type
    \Input iCrvType     - key curve type, if ec

    \Output
        int32_t         - zero=success, else failure

    \Version 07/22/2009 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _ProtoSSLVerifyCertificateSignature(SecureStateT *pSecure, X509CertificateT *pCert, const uint8_t *pKeyModData, int32_t iKeyModSize, const uint8_t *pKeyExpData, int32_t iKeyExpSize, int32_t iKeyType, int32_t iCrvType)
{
    int32_t iResult;

    NetPrintfVerbose((DEBUG_VAL_CERT, 0, "protossl: verifying signature\n"));

    // if ecdsa
    if (iKeyType == ASN_OBJ_ECDSA_KEY)
    {
        // verify the signature
        while((iResult = _ProtoSSLVerifySignatureEcdsa(pSecure, pCert->SigData, pCert->iSigSize, pCert->HashData, pCert->iHashSize, pKeyModData, iKeyModSize, iCrvType)) > 0)
            ;
    }
    else
    {
        while((iResult = _ProtoSSLVerifySignatureRSA(pSecure, pCert->SigData, pCert->iSigSize, pCert->iSigType, pCert->HashData, pCert->iHashSize, pCert->eHashType, pCert->iSigSalt,
            _AsnGetHashType(pCert->iMgfHash), pKeyModData, iKeyModSize, pKeyExpData, iKeyExpSize)) > 0)
            ;
    }

    return(iResult);
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLVerifyCertificate

    \Description
        Verify a previously parsed x.509 certificate

    \Input *pState      - module state (may be NULL)
    \Input *pSecure     - secure state
    \Input *pCert       - pointer to certificate to fill in from header data
    \Input bCertIsCA    - is the certificate a CA?

    \Output int32_t     - zero=success, positive=duplicate, else error

    \Version 03/11/2009 (gschaefer)
*/
/********************************************************************************F*/
static int32_t _ProtoSSLVerifyCertificate(ProtoSSLRefT *pState, SecureStateT *pSecure, X509CertificateT *pCert, uint8_t bCertIsCA)
{
    int32_t iResult = 0;

    // if self-signed permitted and certificate is self-signed, then point to its key info (mod+exp)
    if ((bCertIsCA == TRUE) && (_CertificateCompareIdent(&pCert->Subject, &pCert->Issuer, TRUE) == 0))
    {
        if (_ProtoSSLVerifyCertificateSignature(pSecure, pCert, pCert->KeyModData, pCert->iKeyModSize, pCert->KeyExpData, pCert->iKeyExpSize, pCert->iKeyType, pCert->iCrvType) != 0)
        {
            NetPrintf(("protossl: _VerifyCertificate: signature hash mismatch on self-signed cert\n"));
            _CertificateDebugPrint(pCert, "failed cert");
            return(-50);
        }
    }
    else
    {
        ProtoSSLCACertT *pCACert;

        #if DIRTYCODE_LOGGING
        if ((bCertIsCA == TRUE) && (pCert->iCertIsCA == FALSE)) // when ProtoSSLSetCACert* is called
        {
            NetPrintf(("protossl: _VerifyCertificate: warning --- trying to add a non-CA cert as a CA.\n"));
            _CertificateDebugPrint(pCert, "warning cert");
        }
        #endif

        // locate a matching CA
        for (pCACert = _ProtoSSL_CACerts; pCACert != NULL; pCACert = pCACert->pNext)
        {
            // first, compare to see if our subject matches their issuer
            if ((_CertificateCompareIdent(&pCACert->Subject, &pCert->Issuer, (bCertIsCA||pCert->iCertIsCA)) != 0) ||
                ((pCACert->iKeyModSize != pCert->iSigSize) && (pCert->iKeyType != ASN_OBJ_ECDSA_KEY)))
            {
                continue;
            }

            // verify against this CA
            if (_ProtoSSLVerifyCertificateSignature(pSecure, pCert, pCACert->pKeyModData, pCACert->iKeyModSize, pCACert->KeyExpData, pCACert->iKeyExpSize, pCACert->iKeyType, pCACert->iCrvType) != 0)
            {
                continue;
            }

            NetPrintfVerbose((DEBUG_VAL_CERT, 0, "protossl: verifying succeeded\n"));

            // special processing when validating against a GOS CA
            if (pCACert->uFlags & SSL_CACERTFLAG_GOSCA)
            {
                // make sure the domain of the certificate is an EA domain
                if (ds_stricmpwc(pCert->Subject.strCommon, "*.ea.com") && ds_stricmpwc(pCert->Subject.strCommon, "*.easports.com"))
                {
                    return(SSL_ERR_GOSCA_INVALIDUSE);
                }
                // ignore date range validation failure
                if (pSecure->bDateVerifyFailed)
                {
                    NetPrintf(("protossl: ignoring date range validation failure for GOS CA\n"));
                    pSecure->bDateVerifyFailed = FALSE;
                }
            }
            // only CAPROVIDER CAs can be used against gosca
            if (!ds_stricmpwc(pCert->Subject.strCommon, "gosca.ea.com") && !(pCACert->uFlags & SSL_CACERTFLAG_CAPROVIDER))
            {
                return(SSL_ERR_CAPROVIDER_INVALID);
            }

            // if the CA hasn't been verified already, do that now
            if (pCACert->pX509Cert != NULL)
            {
                if ((iResult = _ProtoSSLVerifyCertificate(pState, pSecure, pCACert->pX509Cert, TRUE)) == 0)
                {
                    #if DIRTYCODE_LOGGING
                    char strIdentSubject[512], strIdentIssuer[512];
                    int32_t iVerbose = (pState != NULL) ? pState->iVerbose : 0;
                    NetPrintfVerbose((iVerbose, 0, "protossl: ca (%s) validated by ca (%s)\n", _CertificateDebugFormatIdent(&pCACert->pX509Cert->Subject, strIdentSubject, sizeof(strIdentSubject)),
                        _CertificateDebugFormatIdent(&pCACert->pX509Cert->Issuer, strIdentIssuer, sizeof(strIdentIssuer))));
                    #endif

                    // cert successfully verified
                    DirtyMemFree(pCACert->pX509Cert, PROTOSSL_MEMID, pCACert->iMemGroup, pCACert->pMemGroupUserData);
                    pCACert->pX509Cert = NULL;
                }
                else
                {
                    _CertificateSetFailureInfo(pState, pCACert->pX509Cert, TRUE);
                    continue;
                }
            }

            // verified
            break;
        }
        if (pCACert == NULL)
        {
            #if DIRTYCODE_LOGGING
            // output debug logging only if we're manually loading a CA cert
            if (bCertIsCA)
            {
                _CertificateDebugPrint(pCert, "_VerifyCertificate() -- no CA available for this certificate");
            }
            #endif
            _CertificateSetFailureInfo(pState, pCert, TRUE);
            return(-51);
        }
    }
    // success
    return(iResult);
}

/*
    hmac, building tls1 keys and key material, generating and verifying mac
*/

/*F********************************************************************************/
/*!
    \Function _ProtoSSLDoHmac

    \Description
        Calculates HMAC using CryptHmac

    \Input *pBuffer     - [out] storage for generated MAC
    \Input iBufLen      - size of output
    \Input *pMessage    - input data to hash
    \Input iMessageLen  - size of input data
    \Input *pMessage2   - more input data to hash (optional)
    \Input iMessageLen2 - size of more input data (if pMessage2 != NULL)
    \Input pKey         - seed
    \Input iKeyLen      - seed length
    \Input eHashType    - hash type

    \Version 10/11/2012 (jbrookes)
*/
/********************************************************************************F*/
static void _ProtoSSLDoHmac(uint8_t *pBuffer, int32_t iBufLen, const uint8_t *pMessage, int32_t iMessageLen, const uint8_t *pMessage2, int32_t iMessageLen2, const uint8_t *pKey, int32_t iKeyLen, CryptHashTypeE eHashType)
{
    if (pMessage2 != NULL)
    {
        CryptHmacMsgT Message[2];
        Message[0].pMessage = pMessage;
        Message[0].iMessageLen = iMessageLen;
        Message[1].pMessage = pMessage2;
        Message[1].iMessageLen = iMessageLen2;
        CryptHmacCalcMulti(pBuffer, iBufLen, Message, 2, pKey, iKeyLen, eHashType);
    }
    else
    {
        CryptHmacCalc(pBuffer, iBufLen, pMessage, iMessageLen, pKey, iKeyLen, eHashType);
    }
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLDoPHash

    \Description
        Implements P_hash as defined in https://tools.ietf.org/html/rfc5246#section-5

    \Input *pBuffer     - [out] storage for P_hash result
    \Input iOutLen      - size of output expected
    \Input *pSecret     - secret
    \Input iSecretLen   - length of secret
    \Input *pSeed       - seed
    \Input iSeedLen     - length of seed
    \Input eHashType    - hash type

    \Version 10/11/2012 (jbrookes)
*/
/********************************************************************************F*/
static void _ProtoSSLDoPHash(uint8_t *pBuffer, int32_t iOutLen, const uint8_t *pSecret, int32_t iSecretLen, const uint8_t *pSeed, int32_t iSeedLen, CryptHashTypeE eHashType)
{
    uint8_t aWork[128];
    int32_t iHashSize = CryptHashGetSize(eHashType);

    // A(1)
    _ProtoSSLDoHmac(aWork, sizeof(aWork), pSeed, iSeedLen, NULL, 0, pSecret, iSecretLen, eHashType);
    ds_memcpy(aWork+iHashSize, pSeed, iSeedLen);
    _ProtoSSLDoHmac(pBuffer, iOutLen, aWork, iSeedLen+iHashSize, NULL, 0, pSecret, iSecretLen, eHashType);

    // A(n)
    while (iOutLen > iHashSize)
    {
        uint8_t aWork2[128];

        pBuffer += iHashSize;
        iOutLen -= iHashSize;

        _ProtoSSLDoHmac(aWork2, sizeof(aWork2), aWork, iHashSize, NULL, 0, pSecret, iSecretLen, eHashType);
        ds_memcpy(aWork, aWork2, iHashSize);
        _ProtoSSLDoHmac(pBuffer, iOutLen, aWork, iSeedLen+iHashSize, NULL, 0, pSecret, iSecretLen, eHashType);
    }
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLDoPRF

    \Description
        Implements PRF as defined in https://tools.ietf.org/html/rfc5246#section-5

    \Input *pBuffer     - [out] storage for P_hash result
    \Input iOutLen      - size of output expected
    \Input *pSecret     - secret
    \Input iSecretLen   - length of secret
    \Input *pSeed       - seed
    \Input iSeedLen     - length of seed

    \Version 10/11/2012 (jbrookes)
*/
/********************************************************************************F*/
static void _ProtoSSLDoPRF(uint8_t *pBuffer, int32_t iOutLen, const uint8_t *pSecret, int32_t iSecretLen, const uint8_t *pSeed, int32_t iSeedLen)
{
    uint8_t aMD5Buf[SSL_KEYMATERIAL_LEN], aSHABuf[SSL_KEYMATERIAL_LEN];
    int32_t iLS = iSecretLen/2;         // split secret in two
    int32_t iBufCnt;
    iLS += iSecretLen & 1;              // handle odd secret lengths

    // execute md5 p_hash
    _ProtoSSLDoPHash(aMD5Buf, iOutLen, pSecret, iLS, pSeed, iSeedLen, CRYPTHASH_MD5);

    // execute sha1 p_hash
    _ProtoSSLDoPHash(aSHABuf, iOutLen, pSecret+iLS, iLS, pSeed, iSeedLen, CRYPTHASH_SHA1);

    // execute XOR of MD5 and SHA hashes
    for (iBufCnt = 0; iBufCnt < iOutLen; iBufCnt += 1)
    {
        pBuffer[iBufCnt] = aMD5Buf[iBufCnt] ^ aSHABuf[iBufCnt];
    }
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLBuildKey

    \Description
        Builds key material/master secret for TLS versions prior to 1.3

    \Input *pState      - module state reference
    \Input *pBuffer     - [out] output for generated key data
    \Input iBufSize     - size of output buffer
    \Input *pSource     - source data
    \Input iSourceLen   - length of source data
    \Input *pRandomA    - random data
    \Input *pRandomB    - random data
    \Input iRandomLen   - length of random data
    \Input *pLabel      - label (TLS1 PRF only)
    \Input uSslVersion  - ssl version being used

    \Version 10/11/2012 (jbrookes)
*/
/********************************************************************************F*/
static void _ProtoSSLBuildKey(ProtoSSLRefT *pState, uint8_t *pBuffer, int32_t iBufSize, uint8_t *pSource, int32_t iSourceLen, uint8_t *pRandomA, uint8_t *pRandomB, int32_t iRandomLen, const char *pLabel, uint16_t uSslVersion)
{
    SecureStateT *pSecure = pState->pSecure;
    uint8_t aWork[128]; // must hold at least 13+32+32 bytes

    ds_strnzcpy((char *)aWork, pLabel, sizeof(aWork));
    ds_memcpy(aWork+13, pRandomA, iRandomLen);
    ds_memcpy(aWork+45, pRandomB, iRandomLen);
    if (uSslVersion < SSL3_TLS1_2)
    {
        // ref http://tools.ietf.org/html/rfc2246#section-6.3
        _ProtoSSLDoPRF(pBuffer, iBufSize, pSource, iSourceLen, aWork, 77);
    }
    else
    {
        // ref https://tools.ietf.org/html/rfc5246#section-6.3; tls1.2 drops md5+sha1 prf and uses the cipher prf hash directly instead
        _ProtoSSLDoPHash(pBuffer, iBufSize, pSource, iSourceLen, aWork, 77, (CryptHashTypeE)pSecure->pCipher->uPrfType);
    }

    if (pBuffer == pSecure->MasterKey)
    {
        #if DIRTYCODE_LOGGING
        char strBuf1[65], strBuf2[97];
        #endif  

        #if DEBUG_RAW_DATA
        NetPrintMem(pSecure->PreMasterKey, sizeof(pSecure->PreMasterKey), "PreMaster");
        NetPrintMem(pSecure->MasterKey, sizeof(pSecure->MasterKey), "Master");
        #endif

        // log params for wireshark decrypt
        NetPrintfVerbose((pState->iVerbose, 0, "protossl: CLIENT_RANDOM %s %s\n",
            ds_fmtoctstring(strBuf1, sizeof(strBuf1), pSecure->ClientRandom, sizeof(pSecure->ClientRandom)),
            ds_fmtoctstring(strBuf2, sizeof(strBuf2), pBuffer, iBufSize)));

        // get rid of premaster secret from memory
        ds_memclr(pSecure->PreMasterKey, sizeof(pSecure->PreMasterKey));
    }
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLBuildKeyMaterial

    \Description
        Build and distribute key material from master secret, server
        random, and client random.  Used for TLS1.2 and prior.

    \Input *pState      - module state reference

    \Version 03/19/2012 (jbrookes)
*/
/********************************************************************************F*/
static void _ProtoSSLBuildKeyMaterial(ProtoSSLRefT *pState)
{
    SecureStateT *pSecure = pState->pSecure;
    uint8_t *pData;

    #if DEBUG_RAW_DATA
    NetPrintf(("protossl: building key material\n"));
    NetPrintMem(pSecure->MasterKey, sizeof(pSecure->MasterKey), "MasterKey");
    NetPrintMem(pSecure->ServerRandom, sizeof(pSecure->ServerRandom), "ServerRandom");
    NetPrintMem(pSecure->ClientRandom, sizeof(pSecure->ClientRandom), "ClientRandom");
    #endif

    // build key material; limit to SSL_KEYMATERIAL_LEN for upstream code even though the keyblock is bigger
    _ProtoSSLBuildKey(pState, pSecure->KeyBlock, SSL_KEYMATERIAL_LEN, pSecure->MasterKey, sizeof(pSecure->MasterKey),
        pSecure->ServerRandom, pSecure->ClientRandom, sizeof(pSecure->ServerRandom), "key expansion",
        pSecure->uSslVersion);

    #if DEBUG_RAW_DATA
    NetPrintMem(pSecure->KeyBlock, sizeof(pSecure->KeyBlock), "KeyExpansion");
    #endif

    // distribute the key material
    pData = pSecure->KeyBlock;
    pSecure->pClientMAC = pData;
    pData += pSecure->pCipher->uMac;
    pSecure->pServerMAC = pData;
    pData += pSecure->pCipher->uMac;
    pSecure->pClientKey = pData;
    pData += pSecure->pCipher->uLen;
    pSecure->pServerKey = pData;
    pData += pSecure->pCipher->uLen;
    pSecure->pClientInitVec = pData;
    pData += pSecure->pCipher->uVecSize;
    pSecure->pServerInitVec = pData;
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLGenerateMacSource

    \Description
        Build the MAC (Message Authentication Code) source data

    \Input *pBuffer - [out] storage for generated MAC
    \Input uSeqn    - sequence number
    \Input uType    - message type
    \Input uSslVers - ssl version
    \Input uSize    - data size

    \Output
        uint8_t *   - pointer past end of generated MAC

    \Version 10/11/2012 (jbrookes) Refactored and added TLS support
*/
/********************************************************************************F*/
static uint8_t *_ProtoSSLGenerateMacSource(uint8_t *pBuffer, uint64_t uSeqn, uint32_t uType, uint32_t uSslVers, uint32_t uSize)
{
    *pBuffer++ = (uint8_t)((uSeqn>>56)&255);
    *pBuffer++ = (uint8_t)((uSeqn>>48)&255);
    *pBuffer++ = (uint8_t)((uSeqn>>40)&255);
    *pBuffer++ = (uint8_t)((uSeqn>>32)&255);
    *pBuffer++ = (uint8_t)((uSeqn>>24)&255);
    *pBuffer++ = (uint8_t)((uSeqn>>16)&255);
    *pBuffer++ = (uint8_t)((uSeqn>>8)&255);
    *pBuffer++ = (uint8_t)((uSeqn>>0)&255);
    *pBuffer++ = (uint8_t)uType;
    *pBuffer++ = (uint8_t)(uSslVers>>8);
    *pBuffer++ = (uint8_t)(uSslVers>>0);
    *pBuffer++ = (uint8_t)(uSize>>8);
    *pBuffer++ = (uint8_t)(uSize>>0);
    return(pBuffer);
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLGenerateMac

    \Description
        Generate MAC for secure send when using non-AEAD ciphers

    \Input *pSecure     - secure state
    \Input *pSend       - data to authenticate
    \Input iSize        - size of data to authenticate
    \Input bServer      - TRUE if operating as server, else FALSE

    \Output
        int32_t         - size of authenticated data plus MAC

    \Version 03/02/2018 (jbrookes) Refactored from _ProtoSSLSendPacket()
*/
/********************************************************************************F*/
static int32_t _ProtoSSLGenerateMac(SecureStateT *pSecure, uint8_t *pSend, int32_t iSize, uint8_t bServer)
{
    uint8_t MacTemp[CRYPTHASH_MAXDIGEST], *pMacTemp;

    // generate the mac source
    pMacTemp = _ProtoSSLGenerateMacSource(MacTemp, pSecure->uSendSeqn, pSecure->SendData[0], pSecure->uSslVersion, (uint32_t)iSize);

    // hash the mac and append to send data
    _ProtoSSLDoHmac(pSend+iSize, pSecure->pCipher->uMac, MacTemp, (int32_t)(pMacTemp-MacTemp), pSend, iSize,
        bServer ? pSecure->pServerMAC : pSecure->pClientMAC,
        pSecure->pCipher->uMac, (CryptHashTypeE)pSecure->pCipher->uMacType);

    // return size+mac to caller
    return(iSize+pSecure->pCipher->uMac);
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLVerifyMac

    \Description
        Verify MAC for secure recv when using non-AEAD ciphers

    \Input *pSecure     - secure state
    \Input iSize        - size of data to authenticate
    \Input bServer      - TRUE if operating as server, else FALSE
    \Input *pBadMac     - [in/out] bad MAC flag

    \Output
        int32_t         - zero=success, negative=failure

    \Version 03/02/2018 (jbrookes) Refactored from _ProtoSSLSendPacket()
*/
/********************************************************************************F*/
static int32_t _ProtoSSLVerifyMac(SecureStateT *pSecure, int32_t iSize, uint8_t bServer, uint8_t *pBadMac)
{
    uint8_t MacTemp[CRYPTHASH_MAXDIGEST], *pMacTemp;
    uint8_t bBadMac;

    // make sure there is room for mac
    if (iSize >= (int32_t)pSecure->pCipher->uMac)
    {
        iSize -= pSecure->pCipher->uMac;
        // remove the mac from size
        pSecure->iRecvProg = pSecure->iRecvSize = pSecure->iRecvBase+iSize;
    }
    else
    {
        NetPrintf(("protossl: _ProtoSSLVerifyMac: no room for mac (%d < %d)\n", iSize, pSecure->pCipher->uMac));
        *pBadMac = TRUE;
        return(-1);
    }

    // generate MAC source
    pMacTemp = _ProtoSSLGenerateMacSource(MacTemp, pSecure->uRecvSeqn, pSecure->RecvHead[0], pSecure->uSslVersion, (uint32_t)iSize);

    // do the hash
    _ProtoSSLDoHmac(MacTemp, pSecure->pCipher->uMac, MacTemp, (int32_t)(pMacTemp-MacTemp),
        pSecure->RecvData+pSecure->iRecvBase, pSecure->iRecvSize-pSecure->iRecvBase,
        bServer ? pSecure->pClientMAC : pSecure->pServerMAC,
        pSecure->pCipher->uMac, (CryptHashTypeE)pSecure->pCipher->uMacType);

    // validate MAC
    bBadMac = memcmp(MacTemp, pSecure->RecvData+pSecure->iRecvSize, pSecure->pCipher->uMac) ? TRUE : FALSE;
    // accumulate MAC flag
    *pBadMac |= bBadMac;
    // return success or failure to caller
    return(!(*pBadMac) ? 0 : -1);
}

/*
    handshake hash management
*/

/*F********************************************************************************/
/*!
    \Function _ProtoSSLHandshakeHashInit

    \Description
        Init handshake hash states

    \Input *pSecure - secure state ref

    \Version 03/21/2017 (jbrookes)
*/
/********************************************************************************F*/
static void _ProtoSSLHandshakeHashInit(SecureStateT *pSecure)
{
    CryptMD5Init(&pSecure->HandshakeMD5);
    CryptSha1Init(&pSecure->HandshakeSHA);
    CryptSha2Init(&pSecure->HandshakeSHA256, SSL3_MAC_SHA256);
    CryptSha2Init(&pSecure->HandshakeSHA384, SSL3_MAC_SHA384);
    CryptSha2Init(&pSecure->HandshakeSHA512, SSL3_MAC_SHA512);
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLHandshakeHashUpdate

    \Description
        Update handshake hash with specified data

    \Input *pSecure - secure state ref
    \Input *pData   - data to add to hash
    \Input iSize    - size of data to add
    \Input *pLabel  - debug label

    \Version 03/21/2017 (jbrookes)
*/
/********************************************************************************F*/
static void _ProtoSSLHandshakeHashUpdate(SecureStateT *pSecure, const uint8_t *pData, int32_t iSize, const char *pLabel)
{
    NetPrintfVerbose((DEBUG_MSG_LIST, 0, "protossl: %s handshake update (size=%d)\n", pLabel, iSize));
    CryptMD5Update(&pSecure->HandshakeMD5, pData, iSize);
    CryptSha1Update(&pSecure->HandshakeSHA, pData, iSize);
    CryptSha2Update(&pSecure->HandshakeSHA256, pData, iSize);
    CryptSha2Update(&pSecure->HandshakeSHA384, pData, iSize);
    CryptSha2Update(&pSecure->HandshakeSHA512, pData, iSize);

    #if DEBUG_RAW_DATA
    NetPrintMem(pData, iSize, "handshake data");
    #endif
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLHandshakeHashGet

    \Description
        Get the current handshake hash, for the specified hash type

    \Input *pSecure     - secure state ref
    \Input eHashType    - hash type to get
    \Input *pBuffer     - [out] buffer to store hash
    \Input iBufSize     - size of output buffer (should be bigger than hash size)

    \Output
        int32_t         - size of hash data, or zero on failure

    \Version 03/21/2017 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _ProtoSSLHandshakeHashGet(SecureStateT *pSecure, CryptHashTypeE eHashType, uint8_t *pBuffer, int32_t iBufSize)
{
    CryptMD5T MD5Context;
    CryptSha1T SHA1Context;
    CryptSha2T SHA2Context;
    int32_t iHashSize;

    if ((iHashSize = CryptHashGetSize(eHashType)) < 0)
    {
        NetPrintf(("protossl: handshake hash type %d unknown\n", eHashType));
        ds_memclr(pBuffer, iBufSize);
        return(0);
    }
    else if (iHashSize > iBufSize)
    {
        NetPrintf(("protossl: handshake hash too large for buffer; truncating\n"));
        iHashSize = iBufSize;
    }

    switch (eHashType)
    {
        case CRYPTHASH_MD5:
            ds_memcpy_s(&MD5Context, sizeof(MD5Context), &pSecure->HandshakeMD5, sizeof(pSecure->HandshakeMD5));
            CryptMD5Final(&MD5Context, pBuffer, iHashSize);
            break;
        case CRYPTHASH_SHA1:
            ds_memcpy_s(&SHA1Context, sizeof(SHA1Context), &pSecure->HandshakeSHA, sizeof(pSecure->HandshakeSHA));
            CryptSha1Final(&SHA1Context, pBuffer, iHashSize);
            break;
        case CRYPTHASH_SHA256:
            ds_memcpy(&SHA2Context, &pSecure->HandshakeSHA256, sizeof(pSecure->HandshakeSHA256));
            CryptSha2Final(&SHA2Context, pBuffer, iHashSize);
            break;
        case CRYPTHASH_SHA384:
            ds_memcpy(&SHA2Context, &pSecure->HandshakeSHA384, sizeof(pSecure->HandshakeSHA384));
            CryptSha2Final(&SHA2Context, pBuffer, iHashSize);
            break;
        case CRYPTHASH_SHA512:
            ds_memcpy(&SHA2Context, &pSecure->HandshakeSHA512, sizeof(pSecure->HandshakeSHA512));
            CryptSha2Final(&SHA2Context, pBuffer, iHashSize);
            break;
        default:
            break;
    }

    return(iHashSize);
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLHandshakeHashInject

    \Description
        Inject synthetic message hash in Hello Retry Request as per
        https://tools.ietf.org/html/rfc8446#section-4.4.1

    \Input *pSecure - secure state ref
    \Input eHashType - hash type to update

    \Version 08/10/2017 (jbrookes)
*/
/********************************************************************************F*/
static void _ProtoSSLHandshakeHashInject(SecureStateT *pSecure, CryptHashTypeE eHashType)
{
    uint8_t aHash[CRYPTHASH_MAXDIGEST], aHead[4];
    CryptSha2T *pHandshakeCtx;
    int32_t iHashSize = CryptHashGetSize(eHashType);

    // get handshake context
    pHandshakeCtx = (eHashType == CRYPTHASH_SHA256) ? &pSecure->HandshakeSHA256 : &pSecure->HandshakeSHA384;

    // get and reinit current hash
    CryptSha2Final(pHandshakeCtx, aHash, iHashSize);
    CryptSha2Init(pHandshakeCtx, iHashSize);

    // create synthetic message header
    aHead[0] = SSL3_MSG_MESSAGE_HASH;
    aHead[1] = 0;
    aHead[2] = 0;
    aHead[3] = (uint8_t)iHashSize;

    #if DEBUG_RAW_DATA
    NetPrintMem(aHead, sizeof(aHead), "msghead");
    NetPrintMem(aHash, iHashSize, "msghash");
    #endif

    // hash the message header
    CryptSha2Update(pHandshakeCtx, aHead, sizeof(aHead));
    // and the message hash
    CryptSha2Update(pHandshakeCtx, aHash, iHashSize);
}

/*
    tls 1.3 key schedule
*/

/*F********************************************************************************/
/*!
    \Function _ProtoSSLHkdfExtract

    \Description
        Implements HKDF-Extract as defined in rfc 5869; see
        https://tools.ietf.org/html/rfc5869#section-2.2

    \Input *pOutput     - [out] output of extract operation
    \Input iOutputLen   - desired length of output
    \Input *pSalt       - salt input to extract
    \Input iSaltLen     - salt length
    \Input *pKey        - key input to extract
    \Input iKeyLen      - length of key
    \Input eHashType    - hash to use for extract

    \Version 03/08/2017 (jbrookes)
*/
/********************************************************************************F*/
static void _ProtoSSLHkdfExtract(uint8_t *pOutput, int32_t iOutputLen, const uint8_t *pSalt, int32_t iSaltLen, const uint8_t *pKey, int32_t iKeyLen, CryptHashTypeE eHashType)
{
    // the RFC defines this as HMAC-Hash(salt, key) but our CryptHmac params are reversed
    CryptHmacCalc(pOutput, iOutputLen, pKey, iKeyLen, pSalt, iSaltLen, eHashType);
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLHkdfExpand

    \Description
        Implements HKDF-Expand as defined in rfc 5869; see
        https://tools.ietf.org/html/rfc5869#section-2.3

    \Input *pOutput     - [out] output of expand operation
    \Input iOutputLen   - desired length of output
    \Input *pKey        - key input to expand (prk)
    \Input iKeyLen      - length of key
    \Input *pInfo       - info input to expand
    \Input iInfoLen     - length of info
    \Input eHashType    - hash to use for expand

    \Version 03/08/2017 (jbrookes)
*/
/********************************************************************************F*/
static void _ProtoSSLHkdfExpand(uint8_t *pOutput, int32_t iOutputLen, const uint8_t *pKey, int32_t iKeyLen, const uint8_t *pInfo, int32_t iInfoLen, CryptHashTypeE eHashType)
{
    uint32_t uRound, uNumRounds;
    int32_t iHashLen = CryptHashGetSize(eHashType);
    uint8_t uPrev, *pPrev;
    uint32_t uPrevLen;
    CryptHmacMsgT Message[3];
    uint8_t uCount;

    // N = ceil(L/HashLen)
    uNumRounds = (iOutputLen/ iHashLen);
    uNumRounds += iOutputLen % iHashLen != 0 ? 1 : 0;
    // T(0) = empty string (zero length)
    uPrev = 0;
    pPrev = &uPrev;
    uPrevLen = 0;
    // T(1) ... T(N)
    for (uRound = 1; uRound <= uNumRounds; uRound += 1, pOutput += iHashLen, iOutputLen -= iHashLen)
    {
        uCount = (uint8_t)uRound;

        Message[0].pMessage = pPrev;
        Message[0].iMessageLen = uPrevLen;
        Message[1].pMessage = pInfo;
        Message[1].iMessageLen = iInfoLen;
        Message[2].pMessage = &uCount;
        Message[2].iMessageLen = sizeof(uCount);
        CryptHmacCalcMulti(pOutput, iOutputLen, Message, sizeof(Message)/sizeof(Message[0]), pKey, iKeyLen, eHashType);

        pPrev = pOutput;
        uPrevLen = iHashLen;
    }
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLHkdfExpandLabel

    \Description
        Implements HKDF-Expand-Label as per
        https://tools.ietf.org/html/rfc8446#section-7.1

    \Input *pOutput     - [out] output of expand operation
    \Input iOutputLen   - desired length of output
    \Input *pKey        - key input to expand (ikm)
    \Input iKeyLen      - length of key
    \Input *pLabel      - string context label
    \Input *pHashValue  - hash data
    \Input iHashLen     - hash data length
    \Input eHashType    - hash to use for label expand

    \Output
        int32_t         - length of output

    \Version 03/08/2017 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _ProtoSSLHkdfExpandLabel(uint8_t *pOutput, int32_t iOutputLen, const uint8_t *pKey, int32_t iKeyLen, const char *pLabel, const uint8_t *pHashValue, int32_t iHashLen, CryptHashTypeE eHashType)
{
    const char *_strLabelPrefix = "tls13 ";
    const int32_t iPrefixLen = (int32_t)strlen(_strLabelPrefix);
    int32_t iLabelLen = (int32_t)strlen(pLabel);
    uint8_t aInfoBuf[1024];
    int32_t iInfoLen = 0, iBufLen = sizeof(aInfoBuf);

    // HkdfLabel.length -- secret key length u16
    aInfoBuf[iInfoLen++] = (uint8_t)(iOutputLen >> 8);
    aInfoBuf[iInfoLen++] = (uint8_t)(iOutputLen);
    // HkdfLabelT.label -- length-prefixed string
    aInfoBuf[iInfoLen++] = (uint8_t)(iLabelLen + iPrefixLen);
    iInfoLen += ds_snzprintf((char *)aInfoBuf + iInfoLen, iBufLen - iInfoLen, "%s%s", _strLabelPrefix, pLabel);
    // HkdfLabelT.hash_value -- hash data
    aInfoBuf[iInfoLen++] = (uint8_t)iHashLen;
    ds_memcpy_s(aInfoBuf + iInfoLen, iBufLen - iInfoLen, pHashValue, iHashLen);
    iInfoLen += iHashLen;
    
    // do the expand
    if (iInfoLen <= iBufLen)
    {
        _ProtoSSLHkdfExpand(pOutput, iOutputLen, pKey, iKeyLen, aInfoBuf, iInfoLen, eHashType);
        #if DEBUG_RAW_DATA
        NetPrintMem(pKey, iKeyLen, "key");
        NetPrintMem(aInfoBuf, iInfoLen, "info");
        NetPrintMem(pOutput, iOutputLen, "output");
        #endif
    }
    else
    {
        NetPrintf(("protossl: buffer too small in Hkdf expansion\n"));
    }
    // return size of output
    return(iOutputLen);
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLDeriveSecret

    \Description
        Implements Derive-Secret as per
        https://tools.ietf.org/html/rfc8446#section-7.1

    \Input *pSecure     - secure state
    \Input *pOutput     - [out] output of expand operation
    \Input iOutputLen   - desired length of output
    \Input *pKey        - key input to expand (ikm)
    \Input iKeyLen      - length of key
    \Input *pLabel      - string context label
    \Input eHashType    - hash to use for label expand
    \Input iHashLen     - hash data length (zero for empty-message hash)
    \Input *pHashData   - hash data to use (NULL to use current handshake hash)

    \Output
        int32_t         - output length of secret

    \Version 03/08/2017 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _ProtoSSLDeriveSecret(SecureStateT *pSecure, uint8_t *pOutput, int32_t iOutputLen, const uint8_t *pKey, int32_t iKeyLen, const char *pLabel, CryptHashTypeE eHashType, int32_t iHashLen, const uint8_t *pHashData)
{
    uint8_t aHashBuf[CRYPTHASH_MAXDIGEST];

    // zero hash length indicates a request for an empty-message hash
    if (iHashLen == 0)
    {
        const CryptHashT *pHash = CryptHashGet(eHashType);
        uint8_t aHashState[CRYPTHASH_MAXSTATE];
        iHashLen = CryptHashGetSize(eHashType);
        pHash->Init(aHashState, iHashLen);
        pHash->Final(aHashState, aHashBuf, iHashLen);
        pHashData = aHashBuf;
    }
    else if (pHashData == NULL)
    {
        // get handshake hash data
        _ProtoSSLHandshakeHashGet(pSecure, eHashType, aHashBuf, sizeof(aHashBuf));
        pHashData = aHashBuf;
    }
    #if DEBUG_RAW_DATA
    NetPrintMem(pHashData, iHashLen, "handshake hash");
    #endif

    // do the expand & return length of output
    _ProtoSSLHkdfExpandLabel(pOutput, iOutputLen, pKey, iKeyLen, pLabel, pHashData, iHashLen, eHashType);
    return(iOutputLen);
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLBuildSecrets

    \Description
        Builds early, handshake, and master secrets as per
        https://tools.ietf.org/html/rfc8446#section-7.1

    \Input *pSecure     - secure state
    \Input *pHandshakeSecret - [out] buffer to hold handshake secret
    \Input *pMasterSecret - [out] buffer to hold master secret
    \Input *pKey        - ECDHE key used to build handshake/master secret
    \Input iKeyLen      - length of key
    \Input eHashType    - hash to use for label expand
    \Input iHashLen     - hash data length

    \Version 03/21/2017 (jbrookes)
*/
/********************************************************************************F*/
static void _ProtoSSLBuildSecrets(SecureStateT *pSecure, uint8_t *pHandshakeSecret, uint8_t *pMasterSecret, uint8_t *pKey, int32_t iKeyLen, CryptHashTypeE eHashType, int32_t iHashLen)
{
    uint8_t aZeroBuf[CRYPTHASH_MAXDIGEST], aWorkBuf[CRYPTHASH_MAXDIGEST], aWorkBuf2[CRYPTHASH_MAXDIGEST];

    // create zero buf
    ds_memclr(aZeroBuf, iHashLen);

    // HKDF-Extract(PSK|0, 0) (Early Secret)
    _ProtoSSLHkdfExtract(aWorkBuf, iHashLen, aZeroBuf, iHashLen, pSecure->bSessionResume ? pSecure->PreSharedKey : aZeroBuf, iHashLen, eHashType);
    #if DEBUG_RAW_DATA
    NetPrintMem(aWorkBuf, iHashLen, "early secret");
    #endif

    // Derive-Secret(., "derived", "")
    _ProtoSSLDeriveSecret(pSecure, aWorkBuf2, iHashLen, aWorkBuf, iHashLen, "derived", eHashType, 0, NULL);
    // HKDF-Extract(salt, ECDHE)   (Handshake Secret)
    _ProtoSSLHkdfExtract(pHandshakeSecret, iHashLen, aWorkBuf2, iHashLen, pKey, iKeyLen, eHashType);
    #if DEBUG_RAW_DATA
    NetPrintMem(pHandshakeSecret, iHashLen, "handshake secret");
    #endif

    // Derive-Secret(., "derived", "")
    _ProtoSSLDeriveSecret(pSecure, aWorkBuf2, iHashLen, pHandshakeSecret, iHashLen, "derived", eHashType, 0, NULL);
    // HKDF-Extract(salt, 0)   (Master Secret)
    _ProtoSSLHkdfExtract(pMasterSecret, iHashLen, aWorkBuf2, iHashLen, aZeroBuf, iHashLen, eHashType);
    #if DEBUG_RAW_DATA
    NetPrintMem(pMasterSecret, iHashLen, "master secret");
    #endif
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLBuildHandshakeKey

    \Description
        Builds TLS1.3 secrets and keys for handshake as per
        https://tools.ietf.org/html/rfc8446#section-7.1

    \Input *pState      - protossl ref

    \Output
        int32_t         - result of curve secret function

    \Notes
        Secrets, keys, and IVs are stored in the KeyBlock of the SecureStateT
        as follows.  When more than one version of an item exists, later
        versions overwrite earlier versions in place:

            master_secret

            server_secret (early, handshake, application_traffic)
            client_secret (early, handshake, application_traffic)

            server_write_key (handshake, application)
            server_write_iv  (handshake, application)
            client_write_key (handshake, application)
            client_write_iv  (handshake, application)

            resumption_master_secret

        Currently, only EC(DHE) key exchange is supported.

    \Version 03/21/2017 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _ProtoSSLBuildHandshakeKey(ProtoSSLRefT *pState)
{
    SecureStateT *pSecure = pState->pSecure;
    const CryptCurveDhT *pEcc;
    CryptHashTypeE eHashType = pSecure->pCipher->uPrfType;
    int32_t iHashLen = CryptHashGetSize(eHashType);
    uint8_t *pKeyBlock = pSecure->KeyBlock;
    uint8_t aHandshakeSecret[CRYPTHASH_MAXDIGEST];
    uint8_t uZero = 0;
    CryptEccPointT PublicKey;
    int32_t iResult, iSecretSize = 0;

    // init context with key exchange private key, if we have not already done so
    if ((pEcc = _ProtoSSLEccInitContext(pSecure, pSecure->pEllipticCurve)) != NULL)
    {
        uint32_t uCryptUsecs;

        // derive EC(DHE) key for handshake traffic encryption
        pEcc->PointInit(&PublicKey, pSecure->PubKey, pSecure->uPubKeyLength);
        if ((iResult = pEcc->Secret(&pSecure->EccContext, &PublicKey, NULL, &uCryptUsecs)) > 0)
        {
            return(iResult);
        }
        iSecretSize = pEcc->PointFinal(pSecure->EccContext, NULL, TRUE, pSecure->PreMasterKey, sizeof(pSecure->PreMasterKey));
        // done with ECC state
        pSecure->bEccContextInitialized = FALSE;

        NetPrintfVerbose((DEBUG_ENC_PERF, 0, "protossl: SSL Perf (generate key for traffic secret) %dms\n", uCryptUsecs/1000));
        pSecure->uTimer += uCryptUsecs/1000;
    }

    #if DEBUG_RAW_DATA
    NetPrintMem(pSecure->PreMasterKey, iSecretSize, "ec(dhe) key");
    #endif

    // build main secrets used to derive keying secrets; we save the master secret for later generation of application key info
    _ProtoSSLBuildSecrets(pSecure, aHandshakeSecret, pKeyBlock, pSecure->PreMasterKey, iSecretSize, eHashType, iHashLen);
    pKeyBlock += iHashLen;

    // build server and client handshake secrets: https://tools.ietf.org/html/rfc8446#section-7.1

    // build server handshake secret
    pKeyBlock += _ProtoSSLDeriveSecret(pSecure, pSecure->pServerSecret = pKeyBlock, iHashLen, aHandshakeSecret, iHashLen, "s hs traffic", eHashType, iHashLen, NULL);

    // build client handshake secret
    pKeyBlock += _ProtoSSLDeriveSecret(pSecure, pSecure->pClientSecret = pKeyBlock, iHashLen, aHandshakeSecret, iHashLen, "c hs traffic", eHashType, iHashLen, NULL);

    // build server and client handshake key material: https://tools.ietf.org/html/rfc8446#section-7.3

    // [sender]_write_key = HKDF-Expand-Label(Secret, "key", "", key_length)
    pKeyBlock += _ProtoSSLHkdfExpandLabel(pSecure->pServerKey = pKeyBlock, pSecure->pCipher->uLen, pSecure->pServerSecret, iHashLen, "key", &uZero, 0, eHashType);
    // [sender]_write_iv = HKDF-Expand-Label(Secret, "iv", "", iv_length) -- NOTE that iv_length of 12 is the full iv size for the cipher, as the iv is fully derived in TLS1.3
    pKeyBlock += _ProtoSSLHkdfExpandLabel(pSecure->pServerInitVec = pKeyBlock, 12, pSecure->pServerSecret, iHashLen, "iv", &uZero, 0, eHashType);

    // [sender]_write_key = HKDF-Expand-Label(Secret, "key", "", key_length)
    pKeyBlock += _ProtoSSLHkdfExpandLabel(pSecure->pClientKey = pKeyBlock, pSecure->pCipher->uLen, pSecure->pClientSecret, iHashLen, "key", &uZero, 0, eHashType);
    // [sender]_write_iv = HKDF-Expand-Label(Secret, "iv", "", iv_length) -- see note above
    pKeyBlock += _ProtoSSLHkdfExpandLabel(pSecure->pClientInitVec = pKeyBlock, 12, pSecure->pClientSecret, iHashLen, "iv", &uZero, 0, eHashType);

    // save pointer to buffer for resumption secret, derived later
    pSecure->pResumeSecret = pKeyBlock;

    // log params for wireshark decrypt
    #if DIRTYCODE_LOGGING
    if (pState->iVerbose > 0)
    {
        char strBuf1[128], strBuf2[128];
        NetPrintf(("protossl: [%p] CLIENT_RANDOM %s %s\n", pState, ds_fmtoctstring(strBuf1, sizeof(strBuf1), pSecure->ClientRandom, sizeof(pSecure->ClientRandom)),
            ds_fmtoctstring(strBuf2, sizeof(strBuf2), pSecure->PreMasterKey, sizeof(pSecure->PreMasterKey))));
        NetPrintf(("protossl: [%p] CLIENT_HANDSHAKE_TRAFFIC_SECRET %s %s\n", pState, ds_fmtoctstring(strBuf1, sizeof(strBuf1), pSecure->ClientRandom, sizeof(pSecure->ClientRandom)),
            ds_fmtoctstring(strBuf2, sizeof(strBuf2), pSecure->pClientSecret, iHashLen)));
        NetPrintf(("protossl: [%p] SERVER_HANDSHAKE_TRAFFIC_SECRET %s %s\n", pState, ds_fmtoctstring(strBuf1, sizeof(strBuf1), pSecure->ClientRandom, sizeof(pSecure->ClientRandom)),
            ds_fmtoctstring(strBuf2, sizeof(strBuf2), pSecure->pServerSecret, iHashLen)));
    }
    #endif

    // init secure state with handshake traffic keys
    if (pSecure->pCipher->uEnc == SSL3_ENC_GCM)
    {
        CryptGcmInit(&pSecure->ReadGcm, pState->bServer ? pSecure->pClientKey : pSecure->pServerKey, pSecure->pCipher->uLen);
        CryptGcmInit(&pSecure->WriteGcm, pState->bServer ? pSecure->pServerKey : pSecure->pClientKey, pSecure->pCipher->uLen);
    }
    else
    {
        CryptChaChaInit(&pSecure->ReadChaCha, pState->bServer ? pSecure->pClientKey : pSecure->pServerKey, pSecure->pCipher->uLen);
        CryptChaChaInit(&pSecure->WriteChaCha, pState->bServer ? pSecure->pServerKey : pSecure->pClientKey, pSecure->pCipher->uLen);
    }
    // sending and receiving secure from this point
    pSecure->bSendSecure = pSecure->bRecvSecure = TRUE;
    pSecure->uSendSeqn = pSecure->uRecvSeqn = 0;

    // done
    return(0);
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLBuildApplicationKey

    \Description
        Builds TLS1.3 secrets and keys for application data as per
        https://tools.ietf.org/html/rfc8446#section-7.1

    \Input *pState      - protossl ref
    \Input *pSecure     - secure state

    \Notes
        See _ProtoSSLBuildHandshakeKey notes for keyblock layout

    \Version 03/22/2017 (jbrookes)
*/
/********************************************************************************F*/
static void _ProtoSSLBuildApplicationKey(ProtoSSLRefT *pState, SecureStateT *pSecure)
{
    CryptHashTypeE eHashType = pSecure->pCipher->uPrfType;
    int32_t iHashLen = CryptHashGetSize(eHashType);
    uint8_t uZero = 0;

    // build server application traffic secret (note this overwrites the server handshake traffic secret)
    _ProtoSSLDeriveSecret(pSecure, pSecure->pServerSecret, iHashLen, pSecure->KeyBlock, iHashLen, "s ap traffic", eHashType, iHashLen, pSecure->aFinishHash);

    // build client application traffic secret (note this overwrites the client handshake traffic secret)
    _ProtoSSLDeriveSecret(pSecure, pSecure->pClientSecret, iHashLen, pSecure->KeyBlock, iHashLen, "c ap traffic", eHashType, iHashLen, pSecure->aFinishHash);

    // log params for wireshark decrypt        
    #if DIRTYCODE_LOGGING
    if (pState->iVerbose > 0)
    {
        char strBuf1[128], strBuf2[128];
        NetPrintf(("protossl: [%p] CLIENT_TRAFFIC_SECRET_0 %s %s\n", pState, ds_fmtoctstring(strBuf1, sizeof(strBuf1), pSecure->ClientRandom, sizeof(pSecure->ClientRandom)),
            ds_fmtoctstring(strBuf2, sizeof(strBuf2), pSecure->pClientSecret, iHashLen)));
        NetPrintf(("protossl: [%p] SERVER_TRAFFIC_SECRET_0 %s %s\n", pState, ds_fmtoctstring(strBuf1, sizeof(strBuf1), pSecure->ClientRandom, sizeof(pSecure->ClientRandom)),
            ds_fmtoctstring(strBuf2, sizeof(strBuf2), pSecure->pServerSecret, iHashLen)));
    }
    #endif

    // [sender]_write_key = HKDF-Expand-Label(Secret, "key", "", key_length)
    _ProtoSSLHkdfExpandLabel(pSecure->pServerKey, pSecure->pCipher->uLen, pSecure->pServerSecret, iHashLen, "key", &uZero, 0, eHashType);
    // [sender]_write_iv = HKDF-Expand-Label(Secret, "iv", "", iv_length) -- NOTE that iv_length of 12 is the full iv size for the cipher, as the iv is fully derived in TLS1.3
    _ProtoSSLHkdfExpandLabel(pSecure->pServerInitVec, 12, pSecure->pServerSecret, iHashLen, "iv", &uZero, 0, eHashType);

    // [sender]_write_key = HKDF-Expand-Label(Secret, "key", "", key_length)
    _ProtoSSLHkdfExpandLabel(pSecure->pClientKey, pSecure->pCipher->uLen, pSecure->pClientSecret, iHashLen, "key", &uZero, 0, eHashType);
    // [sender]_write_iv = HKDF-Expand-Label(Secret, "iv", "", iv_length) -- see note above
    _ProtoSSLHkdfExpandLabel(pSecure->pClientInitVec, 12, pSecure->pClientSecret, iHashLen, "iv", &uZero, 0, eHashType);

    // build resumption master secret
    _ProtoSSLDeriveSecret(pSecure, pSecure->pResumeSecret, iHashLen, pSecure->KeyBlock, iHashLen, "res master", eHashType, iHashLen, NULL);

    #if DEBUG_RES_SESS
    NetPrintMem(pSecure->KeyBlock, iHashLen, "master secret");
    NetPrintMem(pSecure->pResumeSecret, iHashLen, "resumption_master_secret");
    #endif

    // reinit secure state with application traffic keys
    if (pSecure->pCipher->uEnc == SSL3_ENC_GCM)
    {
        CryptGcmInit(&pSecure->ReadGcm, pState->bServer ? pSecure->pClientKey : pSecure->pServerKey, pSecure->pCipher->uLen);
        CryptGcmInit(&pSecure->WriteGcm, pState->bServer ? pSecure->pServerKey : pSecure->pClientKey, pSecure->pCipher->uLen);
    }
    else
    {
        CryptChaChaInit(&pSecure->ReadChaCha, pState->bServer ? pSecure->pClientKey : pSecure->pServerKey, pSecure->pCipher->uLen);
        CryptChaChaInit(&pSecure->WriteChaCha, pState->bServer ? pSecure->pServerKey : pSecure->pClientKey, pSecure->pCipher->uLen);
    }
    // sending and receiving secure from this point
    pSecure->bSendSecure = pSecure->bRecvSecure = TRUE;
    pSecure->uSendSeqn = pSecure->uRecvSeqn = 0;
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLCalcResumeBinder

    \Description
        Calculate the psk binder as per
        https://tools.ietf.org/html/rfc8446#section-4.2.11.2

    \Input *pState      - protossl ref
    \Input *pBuffer     - [out] buffer to store calculated binder
    \Input iBufLen      - size of output buffer
    \Input *pSessTick   - session ticket
    \Input *pHshkMsgBuf - handshake message buffer
    \Input iHshkMsgLen  - length of handshake message
    \Input iHashSize    - binder hash size

    \Output
        uint8_t *       - pointer past end of binder

    \Version 12/19/2017 (jbrookes)
*/
/********************************************************************************F*/
static uint8_t *_ProtoSSLCalcResumeBinder(ProtoSSLRefT *pState, uint8_t *pBuffer, int32_t iBufLen, const SessionTicketT *pSessTick, const uint8_t *pHshkMsgBuf, int32_t iHshkMsgLen, int32_t iHashSize)
{
    uint8_t aZeroBuf[CRYPTHASH_MAXDIGEST], aWorkBuf[CRYPTHASH_MAXDIGEST], aWorkBuf2[CRYPTHASH_MAXDIGEST], aBinderKey[CRYPTHASH_MAXDIGEST];
    uint8_t aHash[CRYPTHASH_MAXDIGEST], aHashCtx[CRYPTHASH_MAXSTATE];
    uint8_t aMsgHead[] = { SSL3_MSG_CLIENT_HELLO, 0, 0, 0 };
    CryptSha2T *pBinderCtx = (CryptSha2T *)aHashCtx;
    SecureStateT *pSecure = pState->pSecure;

    // calculate buffer length - this includes the binders we haven't written out yet
    iBufLen = iHshkMsgLen + iHashSize + 3;
    // set in clienthello header
    aMsgHead[2] = (uint8_t)(iBufLen>>8);
    aMsgHead[3] = (uint8_t)(iBufLen>>0);

    // create zero buf
    ds_memclr(aZeroBuf, iHashSize);

    // if we're in a HelloRetryRequest flow, we need to include the original [ClientHello...HelloRetryRequest] in the binder hash
    if (pSecure->bHelloRetry)
    {
        // get handshake context
        CryptSha2T *pHandshakeCtx = (pSessTick->eHashType == CRYPTHASH_SHA256) ? &pSecure->HandshakeSHA256 : &pSecure->HandshakeSHA384;
        // copy to binder hash
        ds_memcpy_s(pBinderCtx, sizeof(*pBinderCtx), pHandshakeCtx, sizeof(*pHandshakeCtx));
    }
    else
    {
        // init binder hash
        CryptSha2Init(pBinderCtx, iHashSize);
    }
    // hash the ClientHello message header
    CryptSha2Update(pBinderCtx, aMsgHead, sizeof(aMsgHead));
    // hash the ClientHello message body up to binders list
    CryptSha2Update(pBinderCtx, pHshkMsgBuf, iHshkMsgLen);
    // get binder hash
    CryptSha2Final(pBinderCtx, aHash, iHashSize);

    #if DEBUG_RES_SESS
    #if DEBUG_RAW_DATA
    NetPrintMem(aMsgHead, sizeof(aMsgHead), "message head");
    NetPrintMem(pHshkMsgBuf, iHshkMsgLen, "message body");
    #endif
    NetPrintMem(aHash, iHashSize, "binder hash");
    #endif

    // calculate the binder

    // resumption_key = HKDF-Expand-Label(resumption_key, "resumption", ticket_nonce, ticket_nonce.len)
    _ProtoSSLHkdfExpandLabel(pSecure->PreSharedKey, iHashSize, pSessTick->aResumeKey, iHashSize, "resumption", pSessTick->aTicketNonce, pSessTick->uNonceLen, pSessTick->eHashType);
    // HKDF-Extract(0, PSK) (Early Secret)
    _ProtoSSLHkdfExtract(aWorkBuf, iHashSize, aZeroBuf, iHashSize, pSecure->PreSharedKey, iHashSize, pSessTick->eHashType);
    // Derive-Secret(., "res binder", "")
    _ProtoSSLDeriveSecret(pSecure, aWorkBuf2, iHashSize, aWorkBuf, iHashSize, "res binder", pSessTick->eHashType, 0, NULL);
    // finished_key = HKDF-Expand-Label(binder_key, "finished", "", Hash.length)
    _ProtoSSLHkdfExpandLabel(aBinderKey, iHashSize, aWorkBuf2, iHashSize, "finished", NULL, 0, pSessTick->eHashType);
    // verify_data = HMAC(finished_key, Hash(ClientHello*)
    _ProtoSSLHkdfExtract(pBuffer, iHashSize, aBinderKey, iHashSize, aHash, iHashSize, pSessTick->eHashType);

    #if DEBUG_RES_SESS
    NetPrintMem(pSecure->PreSharedKey, iHashSize, "PSK");
    NetPrintMem(aWorkBuf, iHashSize, "early_secret");
    NetPrintMem(aWorkBuf2, iHashSize, "binder key");
    NetPrintMem(aBinderKey, iHashSize, "finished key");
    NetPrintMem(pBuffer, iHashSize, "verify_data");
    #endif

    // return pointer past end of binder   
    return(pBuffer+iHashSize);
}

/*
    encryption and decryption
*/

/*F********************************************************************************/
/*!
    \Function _ProtoSSLAesEncrypt

    \Description
        Encrypt data with AES

    \Input *pSecure     - secure state
    \Input *pSend       - data to encrypt
    \Input iSize        - size of data to encrypt

    \Output
        int32_t         - size of encrypted data

    \Version 01/14/2018 (jbrookes) Refactored from _ProtoSSLSendPacket()
*/
/********************************************************************************F*/
static int32_t _ProtoSSLAesEncrypt(SecureStateT *pSecure, uint8_t *pSend, int32_t iSize)
{
    int32_t iPadBytes;

    // calculate padding
    if ((iPadBytes = 16 - (iSize % 16)) == 0)
    {
        iPadBytes = 16;
    }

    // set the padding data
    ds_memset(pSend+iSize, iPadBytes-1, iPadBytes);
    iSize += iPadBytes;

    // fill in the explict IV for TLS1.1
    if (pSecure->uSslVersion >= SSL3_TLS1_1)
    {
        pSend -= 16;
        CryptRandGet(pSend, 16);
        #if DEBUG_RAW_DATA
        NetPrintMem(pSend, 16, "explicit IV");
        #endif
        iSize += 16;
    }

    // do the encryption
    CryptAesEncrypt(&pSecure->WriteAes, pSend, iSize);

    // return encrypted size to caller
    return(iSize);
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLAesDecrypt

    \Description
        Decrypt data with AES

    \Input *pSecure     - secure state
    \Input *pRecv       - data to decrypt
    \Input iSize        - size of data to decrypt
    \Input *pBadMac     - [out] bad mac flag

    \Output
        int32_t         - size of decrypted data, or -1 on error

    \Version 01/14/2018 (jbrookes) Refactored from _RecvPacket()
*/
/********************************************************************************F*/
static int32_t _ProtoSSLAesDecrypt(SecureStateT *pSecure, uint8_t *pRecv, int32_t iSize, uint8_t *pBadMac)
{
    int32_t iPad, iPadBytes, iPadStart;

    // decrypt the data
    CryptAesDecrypt(&pSecure->ReadAes, pRecv, iSize);

    // if TLS1.1 or greater, skip explicit IV
    if ((pSecure->uSslVersion >= SSL3_TLS1_1) && (pSecure->iRecvSize >= 16))
    {
        pSecure->iRecvBase += 16;
        pRecv += 16;
        iSize -= 16;
    }

    // read number of pad bytes
    iPadBytes = pRecv[iSize-1];

    /* As per http://tools.ietf.org/html/rfc5246#section-6.2.3.2, padding may be up to 255 bytes
       in length.  Each uint8 in the padding data vector MUST be filled with the padding length
       value, padding MUST be checked, and a padding error MUST result in a bad_record_mac
       alert.  To eliminate a possible timing attack, we note the error here but wait until
       after the MAC is generated to report it. */
    for (iPad = 0, iPadStart = iSize-iPadBytes-1; iPad < iPadBytes; iPad += 1)
    {
        if (pRecv[iPadStart+iPad] != iPadBytes)
        {
            #if DIRTYCODE_LOGGING
            if (!(*pBadMac))
            {
                NetPrintf(("protossl: _ProtoSSLAesDecrypt bad padding (len=%d)\n", iPadBytes));
                NetPrintMem(pRecv+iSize-iPadBytes-1, iPadBytes, "_ProtoSSLAesDecrypt padding");
            }
            #endif
            *pBadMac = TRUE;
            iSize = -1;
            break;
        }
    }

    // remove pad if validation was successful
    if (!(*pBadMac))
    {
        iSize -= iPadBytes+1;
    }
    // return size to caller
    return(iSize);
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLAeadGenerateNonce

    \Description
        Generate AEAD Nonce (IV)

    \Input *pSecure     - secure state
    \Input *pBuffer     - [out] storage for generated nonce
    \Input uSeqn        - sequence number
    \Input *pSeqn       - sequence pointer (NULL to use sequence number)
    \Input *pInitVec    - implicit initialization vector (IV)

    \Version 07/08/2014 (jbrookes)
*/
/********************************************************************************F*/
static void _ProtoSSLAeadGenerateNonce(SecureStateT *pSecure, uint8_t *pBuffer, uint64_t uSeqn, const uint8_t *pSeqn, const uint8_t *pInitVec)
{
    if ((pSecure->uSslVersion < PROTOSSL_VERSION_TLS1_3) && (pSecure->pCipher->uEnc != SSL3_ENC_CHACHA))
    {
        *pBuffer++ = *pInitVec++;
        *pBuffer++ = *pInitVec++;
        *pBuffer++ = *pInitVec++;
        *pBuffer++ = *pInitVec++;
        if (pSeqn == NULL)
        {
            *pBuffer++ = (uint8_t)((uSeqn>>56)&255);
            *pBuffer++ = (uint8_t)((uSeqn>>48)&255);
            *pBuffer++ = (uint8_t)((uSeqn>>40)&255);
            *pBuffer++ = (uint8_t)((uSeqn>>32)&255);
            *pBuffer++ = (uint8_t)((uSeqn>>24)&255);
            *pBuffer++ = (uint8_t)((uSeqn>>16)&255);
            *pBuffer++ = (uint8_t)((uSeqn>>8)&255);
            *pBuffer++ = (uint8_t)((uSeqn>>0)&255);
        }
        else
        {
            ds_memcpy(pBuffer, pSeqn, 8);
        }
    }
    else
    {
        /* tls1.3 (and chacha) nonce generated as per: https://tools.ietf.org/html/rfc8446#section-5.3;
           the 64-bit record number is extended to iv_length, and xor'd with iv */
        uint32_t uCount;
        pBuffer[ 0] = 0;
        pBuffer[ 1] = 0;
        pBuffer[ 2] = 0;
        pBuffer[ 3] = 0;
        pBuffer[ 4] = (uint8_t)((uSeqn>>56)&255);
        pBuffer[ 5] = (uint8_t)((uSeqn>>48)&255);
        pBuffer[ 6] = (uint8_t)((uSeqn>>40)&255);
        pBuffer[ 7] = (uint8_t)((uSeqn>>32)&255);
        pBuffer[ 8] = (uint8_t)((uSeqn>>24)&255);
        pBuffer[ 9] = (uint8_t)((uSeqn>>16)&255);
        pBuffer[10] = (uint8_t)((uSeqn>>8)&255);
        pBuffer[11] = (uint8_t)((uSeqn>>0)&255);
        for (uCount = 0; uCount < 12; uCount += 1)
        {
            pBuffer[uCount] ^= pInitVec[uCount];
        }
    }
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLAeadEncrypt

    \Description
        Encrypt data with an AEAD cipher

    \Input *pSecure     - secure state
    \Input *pSend       - data to encrypt
    \Input iSize        - size of data to encrypt
    \Input bServer      - TRUE if server, else FALSE

    \Output
        int32_t         - size of encrypted data

    \Version 01/14/2018 (jbrookes) Refactored from _ProtoSSLSendPacket()
*/
/********************************************************************************F*/
static int32_t _ProtoSSLAeadEncrypt(SecureStateT *pSecure, uint8_t *pSend, int32_t iSize, uint8_t bServer)
{
    uint8_t AeadData[13], AeadNonce[12], AeadTag[16];
    int32_t iAeadDataSize;

    // change content type to application_data, append content type to output
    if (pSecure->uSslVersion >= PROTOSSL_VERSION_TLS1_3)
    {
        uint8_t uContentType = pSecure->SendData[0];
        pSecure->SendData[0] = SSL3_REC_APPLICATION;
        pSend[iSize++] = uContentType;
    }

    // generate AEAD additional data
    if (pSecure->uSslVersion < PROTOSSL_VERSION_TLS1_3)
    {
        // generate aead data (13 bytes, matches TLS MAC)
        _ProtoSSLGenerateMacSource(AeadData, pSecure->uSendSeqn, pSecure->SendData[0], pSecure->uSslVersion, (uint32_t)iSize);
        iAeadDataSize = sizeof(AeadData);
    }
    else
    {
        /* as per https://tools.ietf.org/html/rfc8446#section-5.2, tls1.3 AEAD data is the record header.
           we already have the first three bytes of the record header formatted in SendData but send length isn't written
           yet, so we calculate it here as the input size plus the tag size */
        int32_t iSendSize = iSize + sizeof(AeadTag);
        AeadData[0] = pSecure->SendData[0];
        AeadData[1] = pSecure->SendData[1];
        AeadData[2] = pSecure->SendData[2];
        AeadData[3] = (uint8_t)(iSendSize>>8);
        AeadData[4] = (uint8_t)(iSendSize>>0);
        iAeadDataSize = 5;
        #if DEBUG_RAW_DATA
        NetPrintMem(AeadData, iAeadDataSize, "AeadData-send");
        #endif
    }

    // generate nonce
    if ((pSecure->uSslVersion < PROTOSSL_VERSION_TLS1_3) && (pSecure->pCipher->uEnc != SSL3_ENC_CHACHA))
    {
        // generate nonce (4 bytes implicit salt from the IV; 8 bytes explicit, we use sequence)
        _ProtoSSLAeadGenerateNonce(pSecure, AeadNonce, pSecure->uSendSeqn, NULL, bServer ? pSecure->pServerInitVec : pSecure->pClientInitVec);
        // write explicit IV to output; this is not encrypted
        ds_memcpy(pSend-8, AeadNonce+4, 8);
        #if DEBUG_RAW_DATA
        NetPrintMem(pSend-8, 8, "explicit IV");
        #endif
    }
    else
    {
        // generate tls1.3/chacha nonce
        _ProtoSSLAeadGenerateNonce(pSecure, AeadNonce, pSecure->uSendSeqn, NULL, bServer ? pSecure->pServerInitVec : pSecure->pClientInitVec);
    }

    // do the encryption
    if (pSecure->pCipher->uEnc == SSL3_ENC_GCM)
    {
        iSize = CryptGcmEncrypt(&pSecure->WriteGcm, pSend, iSize, AeadNonce, sizeof(AeadNonce), AeadData, iAeadDataSize, AeadTag, sizeof(AeadTag));
    }
    else
    {
        iSize = CryptChaChaEncrypt(&pSecure->WriteChaCha, pSend, iSize, AeadNonce, sizeof(AeadNonce), AeadData, iAeadDataSize, AeadTag, sizeof(AeadTag));
    }

    // append tag to output
    ds_memcpy(pSend+iSize, AeadTag, sizeof(AeadTag));
    iSize += sizeof(AeadTag);

    // add explicit IV to size *after* encrypt+tag
    if ((pSecure->uSslVersion < PROTOSSL_VERSION_TLS1_3) && (pSecure->pCipher->uEnc != SSL3_ENC_CHACHA))
    {
        iSize += 8;
    }

    // return encrypted size to caller
    return(iSize);
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLAeadDecrypt

    \Description
        Decrypt data with an AEAD cipher

    \Input *pSecure     - secure state
    \Input *pRecv       - data to decrypt
    \Input iSize        - size of data to decrypt
    \Input bServer      - TRUE if server, else FALSE
    \Input *pBadMac     - [out] bad mac flag
    \Input *pAlert      - [out] alert, on error

    \Output
        int32_t         - size of decrypted data, or -1 on error

    \Version 01/14/2018 (jbrookes) Refactored from _RecvPacket()
*/
/********************************************************************************F*/
static int32_t _ProtoSSLAeadDecrypt(SecureStateT *pSecure, uint8_t *pRecv, int32_t iSize, uint8_t bServer, uint8_t *pBadMac, int32_t *pAlert)
{
    uint8_t AeadData[13], AeadNonce[12];
    int32_t iAeadDataSize;

    // generate nonce
    if ((pSecure->uSslVersion < PROTOSSL_VERSION_TLS1_3) && (pSecure->pCipher->uEnc != SSL3_ENC_CHACHA))
    {
        // generate nonce (4 bytes implicit salt from TLS IV; 8 bytes explicit from packet)
        _ProtoSSLAeadGenerateNonce(pSecure, AeadNonce, 0, pRecv, bServer ? pSecure->pClientInitVec : pSecure->pServerInitVec);

        // skip explicit IV in packet data
        pSecure->iRecvBase += 8;
        pRecv += 8;
    }
    else 
    {
        // generate tls1.3/chacha nonce
        _ProtoSSLAeadGenerateNonce(pSecure, AeadNonce, pSecure->uRecvSeqn, NULL, bServer ? pSecure->pClientInitVec : pSecure->pServerInitVec);
    }

    // remove authentication tag
    pSecure->iRecvSize -= 16;
    pSecure->iRecvProg = pSecure->iRecvSize;

    // recalculate size
    iSize = pSecure->iRecvSize - pSecure->iRecvBase;

    // generate AEAD additional data
    if (pSecure->uSslVersion < PROTOSSL_VERSION_TLS1_3)
    {
        // generate aead data (13 bytes, matches TLS MAC)
        _ProtoSSLGenerateMacSource(AeadData, pSecure->uRecvSeqn, pSecure->RecvHead[0], pSecure->uSslVersion, (uint32_t)iSize);
        iAeadDataSize = sizeof(AeadData);
    }
    else
    {
        // as per https://tools.ietf.org/html/rfc8446#section-5.2, tls1.3 AEAD data is the record header
        iAeadDataSize = sizeof(pSecure->RecvHead);
        ds_memcpy_s(AeadData, sizeof(AeadData), pSecure->RecvHead, iAeadDataSize);
        #if DEBUG_RAW_DATA
        NetPrintMem(AeadData, iAeadDataSize, "AeadData-recv");
        #endif
    }

    // decrypt data
    if (pSecure->pCipher->uEnc == SSL3_ENC_GCM)
    {
        iSize = CryptGcmDecrypt(&pSecure->ReadGcm, pRecv, iSize, AeadNonce, sizeof(AeadNonce), AeadData, iAeadDataSize, pRecv+iSize, 16);
    }
    else
    {
         iSize = CryptChaChaDecrypt(&pSecure->ReadChaCha, pRecv, iSize, AeadNonce, sizeof(AeadNonce), AeadData, iAeadDataSize, pRecv+iSize, 16);
    }

    /* as per http://tools.ietf.org/html/rfc5288#section-3: Implementations MUST send TLS Alert bad_record_mac for all types of
       failures encountered in processing the AES-GCM algorithm; this gets handled later in the receive flow */
    if (iSize < 0)
    {
        NetPrintf(("protossl: aead decrypt of received data failed\n"));
        *pBadMac = TRUE;
    }

    // for tls1.3, handle padding and content-type extraction
    if ((pSecure->uSslVersion >= PROTOSSL_VERSION_TLS1_3) && !(*pBadMac))
    {
        // handle record padding as per https://tools.ietf.org/html/rfc8446#section-5.4
        for ( ; (iSize > 0) && (pRecv[iSize-1] == 0); iSize -= 1)
            ;
        // read and overwrite content type
        if (iSize > 0)
        {
            pSecure->iRecvSize = pSecure->iRecvProg = pSecure->iRecvBase+iSize-1;
            pSecure->RecvHead[0] = pSecure->RecvData[pSecure->iRecvSize];
        }
        else
        {
            /* Implementations MUST limit their scanning to the cleartext returned from the AEAD decryption.  If a receiving implementation
               does not find a non-zero octet in the cleartext, it MUST terminate the connection with an "unexpected_message" alert. */
            NetPrintf(("protossl: _ProtoSSLAeadDecrypt: no content-type included in message\n"));
            *pAlert = SSL3_ALERT_DESC_UNEXPECTED_MESSAGE;
            iSize = -1;
        }
    }
    // return size to caller
    return(iSize);
}

/*
    secure packet send and receive
*/

/*F********************************************************************************/
/*!
    \Function _ProtoSSLSendSecure

    \Description
        Send secure queued data

    \Input *pState      - module state
    \Input *pSecure     - secure state

    \Output
        int32_t         - zero if nothing was sent, else one

    \Version 11/07/2013 (jbrookes) Refactored from _ProtoSSLUpdateSend()
*/
/********************************************************************************F*/
static int32_t _ProtoSSLSendSecure(ProtoSSLRefT *pState, SecureStateT *pSecure)
{
    int32_t iResult, iXfer = 0;

    // see if there is data to send
    if (pSecure->iSendProg < pSecure->iSendSize)
    {
        // try to send
        iResult = SocketSend(pState->pSock, (char *)pSecure->SendData+pSecure->iSendProg, pSecure->iSendSize-pSecure->iSendProg, 0);
        if (iResult > 0)
        {
            pSecure->iSendProg += iResult;
            iXfer = 1;
        }
        if (iResult < 0)
        {
            pState->iState = (pState->iState < ST3_SECURE) ? ST_FAIL_SETUP : ST_FAIL_SECURE;
            pState->iClosed = 1;
        }
        // see if the data can be cleared
        if (pSecure->iSendProg == pSecure->iSendSize)
        {
            pSecure->iSendProg = pSecure->iSendSize = 0;
        }
    }

    // return if something was sent
    return(iXfer);
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLSendPacket

    \Description
        Build an outgoing SSL packet. Accepts head/body buffers to allow easy
        contruction of header / data in individual buffers (or set one to null
        if data already combind).

    \Input *pState   - ssl state ptr
    \Input uType     - record type
    \Input *pHeadPtr - pointer to head buffer
    \Input iHeadLen  - length of head buffer
    \Input *pBodyPtr - pointer to data to send
    \Input iBodyLen  - length of data to send

    \Output int32_t - -1=invalid length, zero=no error

    \Version 11/10/2005 gschaefer
*/
/********************************************************************************F*/
static int32_t _ProtoSSLSendPacket(ProtoSSLRefT *pState, uint8_t uType, const void *pHeadPtr, int32_t iHeadLen, const void *pBodyPtr, int32_t iBodyLen)
{
    SecureStateT *pSecure = pState->pSecure;
    uint32_t uRecordVersion;
    uint8_t *pSend;
    int32_t iSize;

    // verify if the input buffer length is good
    if ((iHeadLen + iBodyLen + SSL_SNDOVH_PACKET) > (signed)sizeof(pSecure->SendData))
    {
        NetPrintf(("protossl: _ProtoSSLSendPacket: buffer overflow (iHeadLen=%d, iBodyLen=%d)\n", iHeadLen, iBodyLen));
        return(-1);
    }

    // setup record frame
    pSecure->SendData[0] = uType;
    // get ssl version to embed in header; as per https://tools.ietf.org/html/rfc8446#section-5.1 the record layer version is frozen at 1.2 for TLS1.3+
    uRecordVersion = (pSecure->uSslVersion < SSL3_TLS1_3) ? pSecure->uSslVersion : SSL3_TLS1_2;
    pSecure->SendData[1] = (uint8_t)(uRecordVersion>>8);
    pSecure->SendData[2] = (uint8_t)(uRecordVersion>>0);

    // point to data area
    pSend = pSecure->SendData+5;
    iSize = 0;

    /* reserve space for explicit IV if we're TLS1.1 or greater and are using AES (block cipher).  reserving
       space here means we don't have to shuffle packet data around later.  note that the IV is *not* included
       in calculating the handshake data for the finish packet */
    if (pSecure->bSendSecure && (pSecure->pCipher != NULL) && (pSecure->pCipher->uEnc == SSL3_ENC_AES) && (pSecure->uSslVersion >= SSL3_TLS1_1))
    {
        pSend += 16;
    }
    // reserve space for explicit IV for GCM (TLS1.2 and lower)
    if (pSecure->bSendSecure && (pSecure->pCipher != NULL) && (pSecure->pCipher->uEnc == SSL3_ENC_GCM) && (pSecure->uSslVersion < SSL3_TLS1_3))
    {
        pSend += 8;
    }

    // copy over the head
    ds_memcpy(pSend+iSize, pHeadPtr, iHeadLen);
    iSize += iHeadLen;

    // copy over the body
    ds_memcpy(pSend+iSize, pBodyPtr, iBodyLen);
    iSize += iBodyLen;

    // hash handshake data for "finish" packet
    if (uType == SSL3_REC_HANDSHAKE)
    {
        _ProtoSSLHandshakeHashUpdate(pSecure, pSend, iSize, "_ProtoSSLSendPacket");
    }

    // handle encryption
    if (pSecure->bSendSecure && (pSecure->pCipher != NULL))
    {
        // add MAC for non-AEAD ciphers
        if (pSecure->pCipher->uMacType != CRYPTHASH_NULL)
        {
            iSize = _ProtoSSLGenerateMac(pSecure, pSend, iSize, pState->bServer);
        }

        NetPrintfVerbose((DEBUG_MSG_LIST, 0, "protossl: _ProtoSSLSendPacket (secure enc=%d mac=%d): type=%d, size=%d, seq=%qd\n",
            pSecure->pCipher->uEnc, pSecure->pCipher->uMac, pSecure->SendData[0], iSize, pSecure->uSendSeqn));
        #if (DEBUG_RAW_DATA > 1)
        NetPrintMem(pSecure->SendData, iSize+5, "_ProtoSSLSendPacket");
        #endif

        // encrypt the data
        if (pSecure->pCipher->uEnc == SSL3_ENC_AES)
        {
            iSize = _ProtoSSLAesEncrypt(pSecure, pSend, iSize);
        }
        if ((pSecure->pCipher->uEnc == SSL3_ENC_GCM) || (pSecure->pCipher->uEnc == SSL3_ENC_CHACHA))
        {
            iSize = _ProtoSSLAeadEncrypt(pSecure, pSend, iSize, pState->bServer);
        }
    }
    else
    {
        NetPrintfVerbose((DEBUG_MSG_LIST, 0, "protossl: _ProtoSSLSendPacket (unsecure): type=%d, size=%d, seq=%qd\n", pSecure->SendData[0], iSize, pSecure->uSendSeqn));
        #if (DEBUG_RAW_DATA > 1)
        NetPrintMem(pSecure->SendData, iSize+5, "_ProtoSSLSendPacket");
        #endif
    }

    // setup total record size
    pSecure->SendData[3] = (uint8_t)(iSize>>8);
    pSecure->SendData[4] = (uint8_t)(iSize>>0);

    // setup buffer pointers
    pSecure->iSendProg = 0;
    pSecure->iSendSize = iSize+5;

    // increment the sequence
    pSecure->uSendSeqn += 1;
    return(0);
}

#if DIRTYCODE_LOGGING
/*F********************************************************************************/
/*!
    \Function _ProtoSSLRecvGetRecordTypeName

    \Description
        Get debug ssl record type name

    \Input uRecordType  - tls record type

    \Output
        const char *    - pointer to record type name, or "unknown" if not recognized

    \Version 11/15/2017 (jbrookes)
*/
/********************************************************************************F*/
static const char *_ProtoSSLRecvGetRecordTypeName(uint32_t uRecordType)
{
    static const char *_ContentTypeNames[5] = { "ChangeCipherSpec", "Alert", "Handshake", "Application", "Heartbeat" };
    const char *pContentType = ((uRecordType >= 20) && (uRecordType <= 24)) ? _ContentTypeNames[uRecordType-20] : "unknown";
    return(pContentType);
}
#endif

#if DIRTYCODE_LOGGING
/*F********************************************************************************/
/*!
    \Function _ProtoSSLRecvGetHandshakeTypeName

    \Description
        Get debug ssl handshake type name

    \Input uHandshakeType  - tls record type

    \Output
        const char *        - pointer to handshake type name, or "unknown" if not recognized

    \Version 05/03/2019 (jbrookes)
*/
/********************************************************************************F*/
static const char *_ProtoSSLRecvGetHandshakeTypeName(uint32_t uHandshakeType)
{
    static const char *_HandshakeTypeNames[] =
    {
        "hello_request", "client_hello", "server_hello", "hello_verify_request",
        "new_session_ticket", "end_of_early_data", "hello_retry_request", "unassigned_7", 
        "encrypted_extensions", "unassigned_9", "unassigned_10", "certificate",
        "server_key_exchange", "certificate_request", "server_hello_done", "certificate_verify",
        "client_key_exchange", "unassigned_17", "unassigned_18", "unassigned_19",
        "finished", "certificate_url", "certificate_status", "supplemental_data",
        "key_update",
        "message_hash"
    };
    const char *pHandshakeType;
    if (uHandshakeType <= SSL3_MSG_KEY_UPDATE)
    {
        pHandshakeType = _HandshakeTypeNames[uHandshakeType];
    }
    else if (uHandshakeType == SSL3_MSG_MESSAGE_HASH)
    {
        pHandshakeType = _HandshakeTypeNames[SSL3_MSG_KEY_UPDATE+1];
    }
    else
    {
        pHandshakeType = "unknown";
    }
    return(pHandshakeType);
}
#endif

/*F********************************************************************************/
/*!
    \Function _ProtoSSLRecvReset

    \Description
        Reset receive tracking; called when we're done processing a packet or
        in an error condition

    \Input *pSecure - secure state

    \Version 11/14/2017 (jbrookes)
*/
/********************************************************************************F*/
static inline void _ProtoSSLRecvReset(SecureStateT *pSecure)
{
    pSecure->iRecvHead = pSecure->iRecvProg = pSecure->iRecvSize = pSecure->iRecvBase = pSecure->iRecvHshkProg = 0;
    pSecure->bRecvProc = FALSE;
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLRecvPacket

    \Description
        Decode ssl3 record header, decrypt data, verify mac.

    \Input *pState  - ssl state ptr
    \Input *pAlert  - [out] alert, on error

    \Output
        int32_t     - 0 for success, negative for error

    \Version 11/10/2005 (gschaefer)
*/
/********************************************************************************F*/
static int32_t _ProtoSSLRecvPacket(ProtoSSLRefT *pState, int32_t *pAlert)
{
    SecureStateT *pSecure = pState->pSecure;
    int32_t iSize = pSecure->iRecvSize-pSecure->iRecvBase;
    uint8_t bBadMac = FALSE;

    /* check the record type - as per http://tools.ietf.org/html/rfc5246#section-6, if a TLS implementation
       receives an unexpected record type, it MUST send an unexpected_message alert */
    if ((pSecure->RecvHead[0] < SSL3_REC_CIPHER) || (pSecure->RecvHead[0] > SSL3_REC_APPLICATION))
    {
        NetPrintf(("protossl: _RecvPacket: unknown record type (%s) %d\n", _ProtoSSLRecvGetRecordTypeName(pSecure->RecvHead[0]), pSecure->RecvHead[0]));
        *pAlert = SSL3_ALERT_DESC_UNEXPECTED_MESSAGE;
        return(-1);
    }

    /* as per https://tools.ietf.org/html/rfc8446#section-5 check for change_cipher_spec record; if 
       we find one, we ignore it */
    if ((pSecure->uSslVersion >= PROTOSSL_VERSION_TLS1_3) && (pSecure->RecvHead[0] == SSL3_REC_CIPHER))
    {
        // validate ccs message
        if ((iSize != 1) || (pSecure->RecvData[0] != 1))
        {
            NetPrintf(("protossl: invalid change_cipher_spec message in tls1.3 flow\n"));
            *pAlert = SSL3_ALERT_DESC_DECODE_ERROR;
            return(-1);
        }
        else
        {
            NetPrintfVerbose((DEBUG_MSG_LIST, 0, "protossl: skipping dummy change_cipher_spec message in tls1.3 flow\n"));
            pSecure->iRecvHead = pSecure->iRecvProg = pSecure->iRecvSize = pSecure->iRecvBase = pSecure->iRecvHshkProg = 0;
            return(0);
        }
    }

    // if we are reciving the finished packet, we need to transition to secure receiving
    if (pState->iState == ST3_RECV_FINISH)
    {
        pSecure->bRecvSecure = TRUE;
    }

    // handle decryption
    if (pSecure->bRecvSecure && (pSecure->pCipher != NULL))
    {
        // decrypt the data
        if (pSecure->pCipher->uEnc == SSL3_ENC_AES)
        {
            iSize = _ProtoSSLAesDecrypt(pSecure, pSecure->RecvData+pSecure->iRecvBase, iSize, &bBadMac);
        }
        if ((pSecure->pCipher->uEnc == SSL3_ENC_GCM) || (pSecure->pCipher->uEnc == SSL3_ENC_CHACHA))
        {
            iSize = _ProtoSSLAeadDecrypt(pSecure, pSecure->RecvData+pSecure->iRecvBase, iSize, pState->bServer, &bBadMac, pAlert);
        }

        // validate MAC for non-AEAD ciphers
        if (pSecure->pCipher->uMacType != CRYPTHASH_NULL)
        {
            iSize = _ProtoSSLVerifyMac(pSecure, iSize, pState->bServer, &bBadMac);
        }

        // handle mac/decrypt errors
        if (iSize < 0)
        {
            if (bBadMac)
            {
                NetPrintf(("protossl: _RecvPacket: bad MAC!\n"));
                *pAlert = SSL3_ALERT_DESC_BAD_RECORD_MAC;
            }
            return(-1);
        }

        NetPrintfVerbose((DEBUG_MSG_LIST, 0, "protossl: _RecvPacket (secure enc=%d mac=%d): type=%s, size=%d, seq=%qd\n",
            pSecure->pCipher->uEnc, pSecure->pCipher->uMac, _ProtoSSLRecvGetRecordTypeName(pSecure->RecvHead[0]), iSize, pSecure->uRecvSeqn));
        #if (DEBUG_RAW_DATA > 1)
        NetPrintMem(pSecure->RecvData, pSecure->iRecvSize, "_RecvPacket");
        #endif
    }
    else
    {
        NetPrintfVerbose((DEBUG_MSG_LIST, 0, "protossl: _RecvPacket (unsecure): type=%s, size=%d, seq=%qd\n", _ProtoSSLRecvGetRecordTypeName(pSecure->RecvHead[0]), iSize, pSecure->uRecvSeqn));
        #if (DEBUG_RAW_DATA > 1)
        NetPrintMem(pSecure->RecvData+pSecure->iRecvBase, iSize, "_RecvPacket");
        #endif
    }

    // increment the sequence number
    pSecure->uRecvSeqn += 1;

    /* check for empty packet; some implementations of SSL emit a zero-length frame followed immediately
       by an n-length frame when TLS1.0 is employed with a CBC cipher like AES.  this is done as a defense
       against the BEAST attack.  we need to detect and clear the empty packet here so the following code
       doesn't use the old packet header with new data */
    if (pSecure->iRecvSize == 0)
    {
        NetPrintfVerbose((DEBUG_MSG_LIST, 0, "protossl: detected empty packet\n"));
        pSecure->iRecvHead = 0;
    }

    // return success
    return(0);
}

/*
    alerts
*/

/*F********************************************************************************/
/*!
    \Function _ProtoSSLGetAlert

    \Description
        Get alert from table based on alert type

    \Input *pState          - module state
    \Input *pAlertDesc      - [out] storage for alert description
    \Input uAlertLevel      - alert level
    \Input uAlertType       - alert type

    \Output
        int32_t             - 0=no alert, 1=alert

    \Version 10/31/2013 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _ProtoSSLGetAlert(ProtoSSLRefT *pState, ProtoSSLAlertDescT *pAlertDesc, uint8_t uAlertLevel, uint8_t uAlertType)
{
    int32_t iErr, iResult = 0;

    // set up default alertdesc
    pAlertDesc->iAlertType = uAlertType;
    pAlertDesc->pAlertDesc = "unknown";

    // find alert description
    if (uAlertLevel != 0)
    {
        for (iErr = 0; _ProtoSSL_AlertList[iErr].iAlertType != -1; iErr += 1)
        {
            if (_ProtoSSL_AlertList[iErr].iAlertType == uAlertType)
            {
                pAlertDesc->pAlertDesc = _ProtoSSL_AlertList[iErr].pAlertDesc;
                iResult = 1;
                break;
            }
        }
    }
    return(iResult);
}

#if DIRTYCODE_LOGGING
/*F********************************************************************************/
/*!
    \Function _ProtoSSLDebugAlert

    \Description
        Debug-only display of info following server alert

    \Input *pState      - module state
    \Input uAlertLevel  - alert level
    \Input uAlertType   - alert type

    \Version 04/04/2009 (jbrookes)
*/
/********************************************************************************F*/
static void _ProtoSSLDebugAlert(ProtoSSLRefT *pState, uint8_t uAlertLevel, uint8_t uAlertType)
{
    ProtoSSLAlertDescT Alert;
    _ProtoSSLGetAlert(pState, &Alert, uAlertLevel, uAlertType);
    NetPrintf(("protossl: ALERT: level=%d, type=%d, name=%s\n", uAlertLevel, uAlertType, Alert.pAlertDesc));
}
#else
#define _ProtoSSLDebugAlert(_pState, _uAlertLevel, _uAlertType)
#endif

/*F********************************************************************************/
/*!
    \Function _ProtoSSLSendAlert

    \Description
        Send an alert

    \Input *pState      - module state reference
    \Input iLevel       - alert level (1=warning, 2=error)
    \Input iValue       - alert value

    \Output
        int32_t         - current state

    \Version 11/06/2013 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _ProtoSSLSendAlert(ProtoSSLRefT *pState, int32_t iLevel, int32_t iValue)
{
    /* $$TODO: we can currently only send if the send state is empty; this should
       be addressed so we can queue an alert for sending at any time */
    if ((pState->pSecure != NULL) && (pState->pSecure->iSendProg == 0) && (pState->pSecure->iSendSize == 0))
    {
        uint8_t strHead[2];
        #if DIRTYCODE_LOGGING
        ProtoSSLAlertDescT Alert;
        _ProtoSSLGetAlert(pState, &Alert, iLevel, iValue);
        #endif
        pState->uAlertLevel = strHead[0] = (uint8_t)iLevel;
        pState->uAlertValue = strHead[1] = (uint8_t)iValue;
        pState->bAlertSent = TRUE;
        NetPrintf(("protossl: sending alert: level=%d type=%s (%d)\n", iLevel, Alert.pAlertDesc, iValue));
        // stage the alert for sending
        _ProtoSSLSendPacket(pState, SSL3_REC_ALERT, strHead, sizeof(strHead), NULL, 0);
        // flush alert
        _ProtoSSLSendSecure(pState, pState->pSecure);
        /* As per http://tools.ietf.org/html/rfc5246#section-7.2.2, any error alert sent
           or received must result in current session information being reset (no resume) */
        if (iLevel == SSL3_ALERT_LEVEL_FATAL)
        {
            _SessionHistoryInvalidate(pState->strHost, SockaddrInGetPort(&pState->PeerAddr));
        }
    }
    else
    {
        NetPrintf(("protossl: unable to send alert (pSecure=%p, iSendProg=%d, iSendSize=%d)\n", pState->pSecure,
            pState->pSecure->iSendProg, pState->pSecure->iSendSize));
    }

    // return current state
    return(pState->iState);
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLRecvAlert

    \Description
        Process an alert message

    \Input *pState      - module state reference
    \Input *pSecure     - secure state reference

    \Version 11/14/2017 (jbrookes)
*/
/********************************************************************************F*/
static void _ProtoSSLRecvAlert(ProtoSSLRefT *pState, SecureStateT *pSecure)
{
    pState->uAlertLevel = pSecure->RecvData[pSecure->iRecvBase];
    pState->uAlertValue = pSecure->RecvData[pSecure->iRecvBase+1];
    pState->bAlertSent = FALSE;

    // process warnings
    if (pState->uAlertLevel == SSL3_ALERT_LEVEL_WARNING)
    {
        if (pState->uAlertValue == SSL3_ALERT_DESC_CLOSE_NOTIFY)
        {
            NetPrintfVerbose((pState->iVerbose, 0, "protossl: received close notification\n"));
            // only an error if we are still in setup
            if (pState->iState < ST3_SECURE)
            {
                pState->iState = ST_FAIL_SETUP;
            }
        }
        if (pState->uAlertValue == SSL3_ALERT_DESC_NO_RENEGOTIATION)
        {
            NetPrintfVerbose((pState->iVerbose, 0, "protossl: received no_renegotiation alert\n"));
            pState->iState = (pState->iState == ST3_RECV_HELLO) ? ST3_SECURE : ST_FAIL_SECURE;
        }
    }
    else // if not a warning, it is an error
    {
        _ProtoSSLDebugAlert(pState, pSecure->RecvData[pSecure->iRecvBase], pSecure->RecvData[pSecure->iRecvBase+1]);
        pState->iState = (pState->iState < ST3_SECURE) ? ST_FAIL_SETUP : ST_FAIL_SECURE;
    }

    // consume the alert message
    _ProtoSSLRecvReset(pSecure);

    // process if the alert resulted in a failure state
    if (pState->iState >= ST_FAIL)
    {
        /* As per http://tools.ietf.org/html/rfc5246#section-7.2.2 any error alert sent or
           received must result in current session information being reset (no resume) */
        _SessionHistoryInvalidate(pState->strHost, SockaddrInGetPort(&pState->PeerAddr));
        pState->iClosed = 1;
    }
}

/*
    handshake utility functions
*/

/*F********************************************************************************/
/*!
    \Function _ProtoSSLGenerateFingerprint

    \Description
        Calculate fingerprint for given data

    \Input *pOutBuf - output buffer to store fingerprint
    \Input iOutSize - size of output buffer
    \Input *pInpData - input data to calculate fingerprint for
    \Input iInpSize - size of input data
    \Input eHashType - hash operation to use

    \Version 04/29/2014 (jbrookes)
*/
/********************************************************************************F*/
static void _ProtoSSLGenerateFingerprint(uint8_t *pOutBuf, int32_t iOutSize, const uint8_t *pInpData, int32_t iInpSize, CryptHashTypeE eHashType)
{
    const CryptHashT *pHash;

    // calculate the fingerprint using designated hash type
    if ((pHash = CryptHashGet(eHashType)) != NULL)
    {
        uint8_t aHashState[CRYPTHASH_MAXSTATE];
        int32_t iHashSize = CryptHashGetSize(eHashType);
        if (iHashSize > iOutSize)
        {
            iHashSize = iOutSize;
        }
        pHash->Init(aHashState, iHashSize);
        pHash->Update(aHashState, pInpData, iInpSize);
        pHash->Final(aHashState, pOutBuf, iHashSize);
        #if DEBUG_VAL_CERT
        NetPrintMem(pOutBuf, iHashSize, "sha1-fingerprint");
        #endif
    }
    else
    {
        NetPrintf(("protossl: _AsnParseCertificate: could not calculate fingerprint\n"));
    }
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLGenerateEccSharedSecret

    \Description
        Generate the ECDHE shared secret if necessary

    \Input *pState      - module state reference

    \Output
        uint8_t         - TRUE=operation complete, FALSE=operation ongoing

    \Version 03/07/2017 (eesponda)
*/
/********************************************************************************F*/
static uint8_t _ProtoSSLGenerateEccSharedSecret(ProtoSSLRefT *pState)
{
    SecureStateT *pSecure = pState->pSecure;
    const CryptCurveDhT *pEcc;
    CryptEccPointT PublicKey;
    int32_t iSecretSize = 0;
    uint32_t uCryptUsecs = 0;

    // only generate the secret if we are using an ECDHE cipher
    if (pSecure->pCipher->uKey != SSL3_KEY_ECDHE)
    {
        return(TRUE);
    }

    /* init context with key exchange private key, if we have not already done so.
       if we fail to get the dh functions then our master secret will be bad which will
       lead to handshake failure */
    if ((pEcc = _ProtoSSLEccInitContext(pSecure, pSecure->pEllipticCurve)) != NULL)
    {
        /* generate shared secret: elliptic curve generation takes multiple frames
           so stay in the same state until the operation is complete */
        pEcc->PointInit(&PublicKey, pSecure->PubKey, pSecure->uPubKeyLength);
        if (pEcc->Secret(&pSecure->EccContext, &PublicKey, NULL, &uCryptUsecs) > 0)
        {
            return(FALSE);
        }
        iSecretSize = pEcc->PointFinal(pSecure->EccContext, NULL, TRUE, pSecure->PreMasterKey, sizeof(pSecure->PreMasterKey));
    }

    NetPrintfVerbose((DEBUG_ENC_PERF, 0, "protossl: SSL Perf (generate shared secret) %dms\n",
        uCryptUsecs/1000));
    pSecure->uTimer += uCryptUsecs/1000;

    // build master secret
    _ProtoSSLBuildKey(pState, pSecure->MasterKey, sizeof(pSecure->MasterKey), pSecure->PreMasterKey, iSecretSize,
        pSecure->ClientRandom, pSecure->ServerRandom, sizeof(pSecure->ClientRandom), "master secret",
        pSecure->uSslVersion);

    // finished with ecc context
    pSecure->bEccContextInitialized = FALSE;
    return(TRUE);
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLGenerateCertificateVerifyHash

    \Description
        Generate a tls1.3 DigitalSignatureHash; see
        https://tools.ietf.org/html/rfc8446#section-4.4.3

    \Input *pBuffer     - [out] storage for generated digitalsignaturehash object
    \Input eHashType    - signature algorithm hash type
    \Input *pHashData   - pointer to hash data to embed in the object
    \Input iHashSize    - size of hash data
    \Input bServer      - TRUE if server, else FALSE
    \Input bSending     - TRUE if sending, else receiving

    \Output
        int32_t         - size of generated hash

    \Version 05/18/2017 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _ProtoSSLGenerateCertificateVerifyHash(uint8_t *pBuffer, CryptHashTypeE eHashType, const uint8_t *pHashData, int32_t iHashSize, uint8_t bServer, uint8_t bSending)
{
    uint8_t aTempBuf[64 + 34 + CRYPTHASH_MAXDIGEST], *pTempBuf = aTempBuf;
    uint8_t aHashState[CRYPTHASH_MAXSTATE];
    const CryptHashT *pHash = CryptHashGet(eHashType);

    // 64 bytes of spaces
    ds_memset(pTempBuf, 32, 64);
    pTempBuf += 64;
    // context string
    pTempBuf += ds_snzprintf((char *)pTempBuf, sizeof(aTempBuf) - 64, "TLS 1.3, %s CertificateVerify", ((bServer && bSending) || (!bServer && !bSending)) ? "server" : "client");
    // zero separator
    *pTempBuf++ = '\0';
    // handshake hash
    ds_memcpy(pTempBuf, pHashData, iHashSize);
    pTempBuf += iHashSize;

    // hash the envelope with the signature algorithm hash (may not match handshake hash)
    iHashSize = CryptHashGetSize(eHashType);
    pHash->Init(aHashState, iHashSize);
    pHash->Update(aHashState, aTempBuf, pTempBuf - aTempBuf);
    pHash->Final(aHashState, pBuffer, iHashSize);

    // return hash size to caller
    return(iHashSize);
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLGenerateFinishHash

    \Description
        Generates finish hash data

    \Input *pBuffer     - [out] storage for generated finish hash
    \Input *pSecure     - secure state
    \Input *pLabelTLS   - label to use for TLS1 finish hash calculation
    \Input bServer      - TRUE if we're the server, else client
    \Input bSending     - TRUE if we're sending the finish hash, else receiving

    \Output
        int32_t         - finish hash size

    \Version 10/11/2012 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _ProtoSSLGenerateFinishHash(uint8_t *pBuffer, SecureStateT *pSecure, const char *pLabelTLS, uint8_t bServer, uint8_t bSending)
{
    uint8_t aMacTemp[128];
    int32_t iHashSize;

    if (pSecure->uSslVersion < SSL3_TLS1_3)
    {
        iHashSize = 12;
        ds_strnzcpy((char *)aMacTemp, pLabelTLS, sizeof(aMacTemp));

        if (pSecure->uSslVersion < SSL3_TLS1_2)
        {
            // setup the finish verification hashes as per https://tools.ietf.org/html/rfc4346#section-7.4.9
            _ProtoSSLHandshakeHashGet(pSecure, CRYPTHASH_MD5, aMacTemp+15, sizeof(aMacTemp)-15);
            _ProtoSSLHandshakeHashGet(pSecure, CRYPTHASH_SHA1, aMacTemp+31, sizeof(aMacTemp)-31);
            _ProtoSSLDoPRF(pBuffer, iHashSize, pSecure->MasterKey, sizeof(pSecure->MasterKey), aMacTemp, 51);
        }
        else
        {
            // setup the finish verification hashes as per https://tools.ietf.org/html/rfc5246#section-7.4.9
            int32_t iHashSize2 = _ProtoSSLHandshakeHashGet(pSecure, pSecure->pCipher->uPrfType, aMacTemp+15, sizeof(aMacTemp)-15);
            _ProtoSSLDoPHash(pBuffer, iHashSize, pSecure->MasterKey, sizeof(pSecure->MasterKey), aMacTemp, 15+iHashSize2, pSecure->pCipher->uPrfType);
        }
    }
    else
    {
        // setup the finish verification hashes as per https://tools.ietf.org/html/rfc8446#section-4.4.4
        uint8_t aFinKey[CRYPTHASH_MAXDIGEST], *pHandshakeSecret;
        // get handshake hash
        iHashSize = _ProtoSSLHandshakeHashGet(pSecure, pSecure->pCipher->uPrfType, aMacTemp, sizeof(aMacTemp));
        // get handshake secret
        pHandshakeSecret = ((bServer && bSending) || (!bServer && !bSending)) ? pSecure->pServerSecret : pSecure->pClientSecret;
        // finished_key = HKDF-Expand-Label(BaseKey, "finished", "", Hash.length)
        _ProtoSSLHkdfExpandLabel(aFinKey, iHashSize, pHandshakeSecret, iHashSize, "finished", aMacTemp, 0, pSecure->pCipher->uPrfType);
        // verify_data = HMAC(finished_key, Hash(Handshake Context + Certificate* + CertificateVerify*)
        _ProtoSSLHkdfExtract(pBuffer, iHashSize, aFinKey, iHashSize, aMacTemp, iHashSize, pSecure->pCipher->uPrfType);
    }

    return(iHashSize);
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLGetSignatureScheme

    \Description
        Get SignatureScheme from two-byte ident

    \Input uIdent                 - ident

    \Output
        const SignatureSchemeT *  - pointer to signature scheme, or NULL

    \Version 05/12/2017 (jbrookes)
*/
/********************************************************************************F*/
static const SignatureSchemeT *_ProtoSSLGetSignatureScheme(const uint16_t uIdent)
{
    const SignatureSchemeT *pSigScheme;
    int32_t iTable;

    for (iTable = 0, pSigScheme = NULL; iTable < (signed)(sizeof(_SSL3_SignatureSchemes) / sizeof(_SSL3_SignatureSchemes[0])); iTable += 1)
    {
        if (_SSL3_SignatureSchemes[iTable].uIdent != uIdent)
        {
            continue;
        }
        pSigScheme = &_SSL3_SignatureSchemes[iTable];
    }
    return(pSigScheme);
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLChooseSignatureScheme

    \Description
        Choose a SignatureScheme from a list

    \Input *pSecure         - secure state
    \Input *pCertificate    - server/client certificate
    \Input *pData           - signature scheme list
    \Input uSigSchemeLen    - length of list in bytes
    \Input *pDataEnd        - safe parse limit

    \Output
        const SignatureSchemeT *  - pointer to signature scheme, or NULL

    \Version 05/12/2017 (jbrookes)
*/
/********************************************************************************F*/
static const SignatureSchemeT *_ProtoSSLChooseSignatureScheme(SecureStateT *pSecure, CertificateDataT *pCertificate, const uint8_t *pData, uint32_t uSigSchemeLen, const uint8_t *pDataEnd)
{
    const SignatureSchemeT *pSigScheme;
    int32_t iSigScheme, iNumSigSchemes;
    uint32_t uCertSigAlg;

    // get signature scheme we need for certificate, if available
    uCertSigAlg = _CertificateGetSigAlg(pCertificate);

    // pick first supported signature scheme
    for (iSigScheme = 0, iNumSigSchemes = (signed)(uSigSchemeLen/sizeof(SignatureAlgorithmT)); iSigScheme < iNumSigSchemes; iSigScheme += 1, pData += 2)
    {
        // skip unsupported signature schemes
        if ((pSigScheme = _ProtoSSLGetSignatureScheme(_SafeRead16(pData, pDataEnd))) == NULL)
        {
            continue;
        }
        // don't pick a signature scheme we don't have a certificate for
        if ((uCertSigAlg != SSL3_SIGALG_NONE) && (pSigScheme->SigAlg.uSigAlg != uCertSigAlg))
        {
            continue;
        }
        // don't allow pss signature schemes with a pkcs1 certificate
        if ((uCertSigAlg == SSL3_SIGALG_RSA) && (pCertificate->iKeyType != pSigScheme->uOidType))
        {
            continue;
        }
        // tls1.3 specific checks
        if (pSecure->uSslVersion >= PROTOSSL_VERSION_TLS1_3)
        {
            // don't allow pkcs1 signature schemes
            if (pSigScheme->uVerifyScheme == SSL3_SIGVERIFY_RSA_PKCS1)
            {
                continue;
            }
            // enforce tls1.3 more restrictive signature schemes based on certificate key type
            if ((uCertSigAlg == SSL3_SIGALG_ECDSA) && (pCertificate->iCrvType != pSigScheme->uOidType))
            {
                continue;
            }
        }
        return(pSigScheme);
    }
    return(NULL);
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLGetCipher

    \Description
        Get cipher from ident, validate that we previously sent it

    \Input *pSecure     - secure state
    \Input uCipherIdent - cipher ident to get cipher for

    \Output
        CipherSuiteT *  - pointer to cipher, or null

    \Version 01/18/2018 (jbrookes)
*/
/********************************************************************************F*/
static const CipherSuiteT *_ProtoSSLGetCipher(SecureStateT *pSecure, uint16_t uCipherIdent)
{
    const CipherSuiteT *pCipher;
    int32_t iIndex;

    // match the cipher from our list
    for (iIndex = 0, pCipher = NULL; (iIndex < (signed)(sizeof(_SSL3_CipherSuite)/sizeof(_SSL3_CipherSuite[0])) && (pCipher == NULL)); iIndex += 1)
    {
        if (uCipherIdent != _SSL3_CipherSuite[iIndex].uIdent)
        {
            continue;
        }
        // validate that we sent it
        if ((_SSL3_CipherSuite[iIndex].uId & pSecure->uSentCiphers) == 0)
        {
            NetPrintf(("protossl: received cipher from server that we did not send\n"));
            break;
        }
        pCipher = &_SSL3_CipherSuite[iIndex];
    }

    // return to caller
    return(pCipher);
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLChooseCipher

    \Description
        Choose cipher from list of idents idents

    \Input *pSecure     - secure state
    \Input *pCertificate - server/client cert, if we have one
    \Input *pCipherList - list of cipher idents
    \Input iNumCiphers  - number of ciphers
    \Input *pDataEnd    - end of data for safe parsing
    \Input uCipherMask  - mask of enabled ciphers
    \Input uCipherPref  - ident of preferred cipher, or zero for no preference

    \Output
        CipherSuiteT *  - pointer to cipher, or null

    \Version 02/19/2018 (jbrookes) Refactored from _ProtoSSLUpdateRecvClientHello
*/
/********************************************************************************F*/
static const CipherSuiteT *_ProtoSSLChooseCipher(SecureStateT *pSecure, CertificateDataT *pCertificate, const uint8_t *pCipherList, int32_t iNumCiphers, const uint8_t *pDataEnd, uint32_t uCipherMask, uint32_t uCipherPref)
{
    const uint8_t *pCipherStart = pCipherList;
    const CipherSuiteT *pCipher;
    int32_t iCipher, iIndex;
    uint16_t uCipherIdent;
    uint32_t uCertSigAlg;

    // get signature algorithm we need for certificate, if available
    uCertSigAlg = _CertificateGetSigAlg(pCertificate);

    // pick first supported cipher
    for (iCipher = 0, iIndex = 0, pSecure->pCipher = NULL; (iCipher < iNumCiphers) && (pSecure->pCipher == NULL); iCipher += 1, pCipherList += 2)
    {
        // read cipher
        uCipherIdent = _SafeRead16(pCipherList, pDataEnd);
        
        // check against our list
        for (iIndex = 0; iIndex < (signed)(sizeof(_SSL3_CipherSuite)/sizeof(_SSL3_CipherSuite[0])); iIndex += 1)
        {
            // ref supported cipher list entry
            pCipher = &_SSL3_CipherSuite[iIndex];

            // skip non-matching ciphers
            if (uCipherIdent != pCipher->uIdent)
            {
                continue;
            }
            // skip non-preferred cipher, if set
            if ((uCipherPref != 0) && (uCipherIdent != uCipherPref))
            {
                continue;
            }
            // skip disabled ciphers
            if ((pCipher->uId & uCipherMask) == 0)
            {
                continue;
            }
            /* skip elliptic curve ciphers if we found no supported elliptic curves in the extensions; for tls1.3
               we let this throogh, and will send a HelloRetryRequest asking for an elliptic curve we support */
            if ((pSecure->uSslVersion < PROTOSSL_VERSION_TLS1_3) && (pCipher->uKey != SSL3_KEY_RSA) && (pSecure->pEllipticCurve == NULL))
            {
                continue;
            }
            // tls1.2 or prior; skip ecdsa/rsa ciphers if we don't have an ecdsa/rsa cert
            if ((pSecure->uSslVersion < PROTOSSL_VERSION_TLS1_3) && (pCipher->uSig != uCertSigAlg))
            {
                continue;
            }
            // skip ciphers that require a newer version of SSL than has been negotiated
            if (pCipher->uMinVers > pSecure->uSslVersion)
            {
                continue;
            }
            // tls1.3 requires tls1.3 ciphers
            if ((pSecure->uSslVersion >= SSL3_TLS1_3) && (pCipher->uMinVers < SSL3_TLS1_3))
            {
                continue;
            }

            // found a cipher
            pSecure->pCipher = pCipher;
            break;
        }
    }
    // if we were looking for a preferred cipher and didn't find it, try again with no preference
    if ((pSecure->pCipher == NULL) && (uCipherPref != 0))
    {
        pSecure->pCipher = _ProtoSSLChooseCipher(pSecure, pCertificate, pCipherStart, iNumCiphers, pDataEnd, uCipherMask, 0);
    }
    return(pSecure->pCipher);
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLChooseCurve

    \Description
        Choose curve based on enabled curves and default curve preferences

    \Input uEnabledCurves   - enabled curves
    \Input iCurveDflt       - preferred default curve

    \Output
        EllipticCurveT *    - pointer to curve, or null

    \Version 06/07/2019 (eesponda)
*/
/********************************************************************************F*/
static const EllipticCurveT *_ProtoSSLChooseCurve(uint32_t uEnabledCurves, int32_t iCurveDflt)
{
    int32_t iEllipticCurve;
    const EllipticCurveT *pEllipticCurve = NULL;

    // if the default curve is enabled and supported use that
    if ((iCurveDflt != -1) && ((_SSL3_EllipticCurves[iCurveDflt].uId & uEnabledCurves) != 0))
    {
        pEllipticCurve = &_SSL3_EllipticCurves[iCurveDflt];
    }
    // otherwise choose the first supported curve in our list
    for (iEllipticCurve = 0; (iEllipticCurve < SSL3_NUM_CURVES) && (pEllipticCurve == NULL); iEllipticCurve += 1)
    {
        // skip disabled curves
        if ((_SSL3_EllipticCurves[iEllipticCurve].uId & uEnabledCurves) == 0)
        {
            continue;
        }
        // found a curve
        pEllipticCurve = &_SSL3_EllipticCurves[iEllipticCurve];
    }
    return(pEllipticCurve);
}

/*
   hello extension writing
*/

/*F********************************************************************************/
/*!
    \Function _ProtoSSLHelloExtnWriteAlpn

    \Description
        Write Application Level Protocol Negotiation (ALPN) extension to ClientHello
        and ServerHello handshake packets; ref http://tools.ietf.org/html/rfc7301

    \Input *pState      - module state reference
    \Input *pBuffer     - buffer to write extension into
    \Input iBufLen      - length of buffer

    \Output
        int32_t         - number of bytes added to buffer

    \Version 08/03/2016 (eesponda)
*/
/********************************************************************************F*/
static int32_t _ProtoSSLHelloExtnWriteAlpn(ProtoSSLRefT *pState, uint8_t *pBuffer, int32_t iBufLen)
{
    uint16_t uAlpnExtensionLength, uExtnLength;
    uint8_t *pBufStart = pBuffer;
    const SecureStateT *pSecure = pState->pSecure;

    // if no protocols, don't write anything into ClientHello
    if ((!pState->bServer) && (pState->uNumAlpnProtocols == 0))
    {
        return(0);
    }
    // if no protocol selected, don't write anything into ServerHello
    else if ((pState->bServer) && ((pSecure == NULL) || (*pSecure->strAlpnProtocol == '\0')))
    {
        return(0);
    }

    // calculate the size of the extension based on if we are client or server
    uAlpnExtensionLength = (!pState->bServer) ? pState->uAlpnExtensionLength : ((uint8_t)strlen(pSecure->strAlpnProtocol) + sizeof(uint8_t));
    uExtnLength = 2+uAlpnExtensionLength; // ident+length+protocol list length

    // make sure we have room
    if ((2+2+uExtnLength) > iBufLen)
    {
        NetPrintf(("protossl: could not add extension alpn; insufficient buffer\n"));
        return(0);
    }

    // extension ident
    *pBuffer++ = (uint8_t)(SSL_EXTN_ALPN>>8);
    *pBuffer++ = (uint8_t)(SSL_EXTN_ALPN>>0);
    // extension length
    *pBuffer++ = (uint8_t)(uExtnLength>>8);
    *pBuffer++ = (uint8_t)(uExtnLength>>0);
    // alpn length
    *pBuffer++ = (uint8_t)(pState->uAlpnExtensionLength>>8);
    *pBuffer++ = (uint8_t)(pState->uAlpnExtensionLength>>0);

    // write the protocols
    if (!pState->bServer)
    {
        int16_t iProtocol;

        // alpn protocols (length + byte string)
        for (iProtocol = 0; iProtocol < pState->uNumAlpnProtocols; iProtocol += 1)
        {
            const AlpnProtocolT *pProtocol = &pState->aAlpnProtocols[iProtocol];

            *pBuffer++ = pProtocol->uLength;
            ds_memcpy(pBuffer, pProtocol->strName, pProtocol->uLength);
            pBuffer += pProtocol->uLength;
        }
    }
    else
    {
        const uint8_t uProtocolLen = uAlpnExtensionLength-(uint8_t)sizeof(uint8_t);

        // alpn protocol
        *pBuffer++ = uProtocolLen;
        ds_memcpy(pBuffer, pSecure->strAlpnProtocol, uProtocolLen);
        pBuffer += uProtocolLen;
    }

    return((int32_t)(pBuffer-pBufStart));
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLHelloExtnWriteCookie

    \Description
        Write Cookie extension, if we have one

    \Input *pState      - module state reference
    \Input *pBuffer     - buffer to write extension into
    \Input iBufLen      - length of buffer

    \Output
        int32_t         - number of bytes added to buffer

    \Version 11/29/2017 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _ProtoSSLHelloExtnWriteCookie(ProtoSSLRefT *pState, uint8_t *pBuffer, int32_t iBufLen)
{
    uint8_t *pBufStart = pBuffer;
    SecureStateT *pSecure = pState->pSecure;
    int32_t iCookieLength;

    // only if we have a cookie
    if (pSecure->pCookie == NULL)
    {
        return(0);
    }

    // read cookie length
    iCookieLength = (pSecure->pCookie[0] << 8) | pSecure->pCookie[1];

    // make sure we have enough room
    if ((2+2+2+iCookieLength) > iBufLen)
    {
        NetPrintf(("protossl: could not add extension cookie; insufficient buffer\n"));
        return(0);
    }

    // extension ident
    *pBuffer++ = (uint8_t)(SSL_EXTN_COOKIE>>8);
    *pBuffer++ = (uint8_t)(SSL_EXTN_COOKIE>>0);
    // extension length
    *pBuffer++ = (uint8_t)((iCookieLength+2)>>8);
    *pBuffer++ = (uint8_t)((iCookieLength+2)>>0);
    // cookie length
    *pBuffer++ = (uint8_t)(iCookieLength>>8);
    *pBuffer++ = (uint8_t)(iCookieLength>>0);
    // add the cookie
    ds_memcpy_s(pBuffer, iBufLen-6, pSecure->pCookie+2, iCookieLength);
    pBuffer += iCookieLength;

    // as per https://tools.ietf.org/html/rfc8446#section-4.2.2 cookies can only be used once
    pSecure->pCookie = NULL;

    return((int32_t)(pBuffer-pBufStart));
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLHelloExtnWriteEllipticCurves

    \Description
        Write the Elliptic Curves / Supported Groups extension to the ClientHello;
        ref http://tools.ietf.org/html/rfc4492

    \Input *pState      - module state reference
    \Input *pBuffer     - buffer to write extension into
    \Input iBufLen      - length of buffer

    \Output
        int32_t         - number of bytes added to buffer

    \Notes
        TLS1.3 calls this "supported_groups", but the actual extension format is
        identical.

    \Version 01/19/2017 (eesponda)
*/
/********************************************************************************F*/
static int32_t _ProtoSSLHelloExtnWriteEllipticCurves(ProtoSSLRefT *pState, uint8_t *pBuffer, int32_t iBufLen)
{
    const uint16_t uMaxEllipticCurvesLength = sizeof(_SSL3_EllipticCurves[0].uIdent)*(sizeof(_SSL3_EllipticCurves)/sizeof(*_SSL3_EllipticCurves));
    uint16_t uEllipticCurvesLength, uExtnLength;
    uint8_t *pBufStart = pBuffer, *pExtnLength, uNumCurves;
    int32_t iEllipticCurve;

    // make sure we have enough room
    if ((2+2+2+uMaxEllipticCurvesLength) > iBufLen)
    {
        NetPrintf(("protossl: could not add extension supported_groups; insufficient buffer\n"));
        return(0);
    }
    // if no curves are enabled don't write anything
    if (pState->uEnabledCurves == 0)
    {
        return(0);
    }

    // extension ident
    *pBuffer++ = (uint8_t)(SSL_EXTN_ELLIPTIC_CURVES>>8);
    *pBuffer++ = (uint8_t)(SSL_EXTN_ELLIPTIC_CURVES>>0);
    // save the location and skip past
    pExtnLength = pBuffer;
    pBuffer += 4;
    // supported elliptic curves
    for (iEllipticCurve = 0, uNumCurves = 0; iEllipticCurve < (signed)(sizeof(_SSL3_EllipticCurves)/sizeof(*_SSL3_EllipticCurves)); iEllipticCurve += 1)
    {
        // skip disabled curves
        if ((_SSL3_EllipticCurves[iEllipticCurve].uId & pState->uEnabledCurves) == 0)
        {
            continue;
        }
        *pBuffer++ = (uint8_t)(_SSL3_EllipticCurves[iEllipticCurve].uIdent>>8);
        *pBuffer++ = (uint8_t)(_SSL3_EllipticCurves[iEllipticCurve].uIdent>>0);
        uNumCurves += 1;
    }
    uEllipticCurvesLength = sizeof(_SSL3_EllipticCurves[0].uIdent)*uNumCurves;
    uExtnLength = uEllipticCurvesLength+2;

    // extension length
    *pExtnLength++ = (uint8_t)(uExtnLength>>8);
    *pExtnLength++ = (uint8_t)(uExtnLength>>0);
    // elliptic curves length
    *pExtnLength++ = (uint8_t)(uEllipticCurvesLength>>8);
    *pExtnLength++ = (uint8_t)(uEllipticCurvesLength>>0);

    return((int32_t)(pBuffer-pBufStart));
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLHelloExtnWriteKeyShare

    \Description
        Write the KeyShare extension to the ClientHello;
        ref https://tools.ietf.org/html/rfc8446#section-4.2.8

    \Input *pState      - module state reference
    \Input *pBuffer     - buffer to write extension into
    \Input iBufLen      - length of buffer

    \Output
        int32_t         - number of bytes added to buffer

    \Version 01/25/2017 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _ProtoSSLHelloExtnWriteKeyShare(ProtoSSLRefT *pState, uint8_t *pBuffer, int32_t iBufLen)
{
    SecureStateT *pSecure = pState->pSecure;
    int32_t iDataLength = 0;
    uint8_t *pBufStart = pBuffer;
    uint8_t aPublicKey[128];
    const EllipticCurveT *pEllipticCurve;

    // ref elliptic curve
    if (((pEllipticCurve = pSecure->pEllipticCurve) == NULL) && (pState->iCurveDflt != -1) && ((_SSL3_EllipticCurves[pState->iCurveDflt].uId & pState->uEnabledCurves) != 0))
    {
        pEllipticCurve = _ProtoSSLChooseCurve(pState->uEnabledCurves, pState->iCurveDflt);
    }

    // set up curve data
    if (pEllipticCurve != NULL)
    {
        const CryptCurveDhT *pEcc;
        int32_t iPublicKeySize = 0;

        // set public key ident
        aPublicKey[0] = (uint8_t)(pEllipticCurve->uIdent>>8);
        aPublicKey[1] = (uint8_t)(pEllipticCurve->uIdent>>0);

        // if server in helloretryrequest flow, use KeyShareHelloRetryRequest value which does not include a key or key size
        if (pState->bServer && (pState->iState == ST3_SEND_HELLO_RETRY))
        {
            iDataLength = 2;
        }
        else
        {
            iDataLength = 4;

            // encode public key generated in Hello into buffer
            if (pSecure->bEccKeyGenerated && ((pEcc = _ProtoSSLEccInitContext(pSecure, pEllipticCurve)) != NULL))
            {
                iPublicKeySize = pEcc->PointFinal(pSecure->EccContext, NULL, FALSE, aPublicKey+4, sizeof(aPublicKey)-4);
                iDataLength += iPublicKeySize;
            }

            // encode public key size 
            aPublicKey[2] = (uint8_t)(iPublicKeySize>>8);
            aPublicKey[3] = (uint8_t)(iPublicKeySize>>0);
        }
    }

    // make sure we have enough room
    if ((2+2+2+iDataLength) > iBufLen)
    {
        NetPrintf(("protossl: could not add extension key_share; insufficient buffer\n"));
        return(0);
    }
    
    // extension ident
    *pBuffer++ = (uint8_t)(SSL_EXTN_KEY_SHARE>>8);
    *pBuffer++ = (uint8_t)(SSL_EXTN_KEY_SHARE>>0);
    // extension length (client only)
    if (!pState->bServer)
    {
        *pBuffer++ = (uint8_t)((iDataLength+2)>>8);
        *pBuffer++ = (uint8_t)((iDataLength+2)>>0);
    }
    // key_share length
    *pBuffer++ = (uint8_t)(iDataLength>>8);
    *pBuffer++ = (uint8_t)(iDataLength>>0);
    // key share data
    if (iDataLength > 0)
    {
        ds_memcpy(pBuffer, aPublicKey, iDataLength);
        pBuffer += iDataLength;
    }
    // return extension size to caller
    return((int32_t)(pBuffer-pBufStart));
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLHelloExtnWritePreSharedKey

    \Description
        Write Pre-Shared Key (PSK) extension to the ClientHello handshake packet;
        ref https://tools.ietf.org/html/rfc8446#section-4.2.11

    \Input *pState      - module state reference
    \Input *pBuffer     - buffer to write extension into
    \Input iBufLen      - length of buffer
    \Input *pHshkMsgBuf - handshake message buffer pointer

    \Output
        int32_t         - number of bytes added to buffer

    \Version 12/07/2017 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _ProtoSSLHelloExtnWritePreSharedKey(ProtoSSLRefT *pState, uint8_t *pBuffer, int32_t iBufLen, const uint8_t *pHshkMsgBuf)
{
    uint8_t *pBufStart = pBuffer, *pHshkMsgEnd;
    const SessionHistoryT *pSessHist;
    const SessionTicketT *pSessTick;
    int32_t iExtnLength, iHashSize; 
    uint32_t uAgeAdd;

    // only add if we have a ticket to match
    if (((pSessHist = _SessionHistoryGet(pState->strHost, SockaddrInGetPort(&pState->PeerAddr), NULL)) == NULL) || !pSessHist->bSessionTicket)
    {
        return(0);
    }
    pSessTick = &pSessHist->SessionTicket;

    // get hash length
    iHashSize = CryptHashGetSize(pSessTick->eHashType);

    // make sure we have enough room
    iExtnLength = 4 + 2 + 2 + pSessTick->uTickLen + 4 + 2 + 1 + iHashSize;
    if (iExtnLength > iBufLen)
    {
        return((iBufLen == 0) ? iExtnLength : 0);
    }

    // pre_shared_Key extension ident
    *pBuffer++ = (uint8_t)(SSL_EXTN_PRE_SHARED_KEY>>8);
    *pBuffer++ = (uint8_t)(SSL_EXTN_PRE_SHARED_KEY>>0);

    // client adds offered psks (we only support one)
    if (!pState->bServer)
    {
        // set pre_shared_key extension length
        *pBuffer++ = (uint8_t)((iExtnLength-4)>>8);
        *pBuffer++ = (uint8_t)((iExtnLength-4)>>0);

        // add psks identity length
        *pBuffer++ = (uint8_t)((2+pSessTick->uTickLen+sizeof(uint32_t))>>8);
        *pBuffer++ = (uint8_t)((2+pSessTick->uTickLen+sizeof(uint32_t))>>0);
        // add identity length
        *pBuffer++ = (uint8_t)(pSessTick->uTickLen>>8);
        *pBuffer++ = (uint8_t)(pSessTick->uTickLen>>0);
        // add ticket 
        ds_memcpy(pBuffer, pSessTick->aTicketData, pSessTick->uTickLen);
        pBuffer += pSessTick->uTickLen;
        // add obfuscated ticket age
        uAgeAdd = (time(NULL) - pSessTick->uRecvTime) * 1000;
        uAgeAdd += pSessTick->uAgeAdd;
        *pBuffer++ = (uint8_t)(uAgeAdd>>24);
        *pBuffer++ = (uint8_t)(uAgeAdd>>16);
        *pBuffer++ = (uint8_t)(uAgeAdd>>8);
        *pBuffer++ = (uint8_t)(uAgeAdd>>0);
        // save handshake message end
        pHshkMsgEnd = pBuffer;
        // add binders length
        *pBuffer++ = (uint8_t)((iHashSize+1)>>8);
        *pBuffer++ = (uint8_t)((iHashSize+1)>>0);
        // add binder length
        *pBuffer++ = (uint8_t)(iHashSize>>0);
        // calculate the binder
        pBuffer = _ProtoSSLCalcResumeBinder(pState, pBuffer, iBufLen, pSessTick, pHshkMsgBuf, pHshkMsgEnd-pHshkMsgBuf, iHashSize);
    }
    else // indicated selected psk - we only support one
    {
        *pBuffer++ = (uint8_t)(2>>8);
        *pBuffer++ = (uint8_t)(2>>0);
        *pBuffer++ = (uint8_t)(0>>8);
        *pBuffer++ = (uint8_t)(0>>0);
    }
    return((int32_t)(pBuffer-pBufStart));
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLHelloExtnWritePreSharedKeyModes

    \Description
        Write Pre-Shared Key Exchange Modes extension to the ClientHello handshake
        packet; ref https://tools.ietf.org/html/rfc8446#section-4.2.9

        This message is client-only

    \Input *pState      - module state reference
    \Input *pBuffer     - buffer to write extension into
    \Input iBufLen      - length of buffer

    \Output
        int32_t         - number of bytes added to buffer

    \Version 12/12/2017 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _ProtoSSLHelloExtnWritePreSharedKeyModes(ProtoSSLRefT *pState, uint8_t *pBuffer, int32_t iBufLen)
{
    const uint16_t uExtnLength = 2;
    uint8_t *pBufStart = pBuffer;

    // make sure we have enough room
    if ((2+2+uExtnLength) > iBufLen)
    {
        NetPrintf(("protossl: could not add extension psk_modes; insufficient buffer\n"));
        return(0);
    }

    // extension ident
    *pBuffer++ = (uint8_t)(SSL_EXTN_PSK_MODES>>8);
    *pBuffer++ = (uint8_t)(SSL_EXTN_PSK_MODES>>0);
    // extension length
    *pBuffer++ = (uint8_t)(uExtnLength>>8);
    *pBuffer++ = (uint8_t)(uExtnLength>>0);
    // psk_mode array length
    *pBuffer++ = 1;
    // psk_modes (only offer psk_dhe_ke; most servers explicitly disallow psk_ke)
    *pBuffer++ = 1;

    return((int32_t)(pBuffer-pBufStart));
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLHelloExtnWriteRenegotiationInfo

    \Description
        Write Renegotiation Info extension to the ClientHello handshake packet;
        ref https://tools.ietf.org/html/rfc5746

    \Input *pState      - module state reference
    \Input *pBuffer     - buffer to write extension into
    \Input iBufLen      - length of buffer

    \Output
        int32_t         - number of bytes added to buffer

    \Notes
        This extension is added strictly for compatibility with sites that require
        it and will fail the connection if it is not present.  ProtoSSL does not
        support renegotiation of any kind and an attempt by a server to renegotiate
        will result in a no_renegotiation alert.

    \Version 03/27/2018 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _ProtoSSLHelloExtnWriteRenegotiationInfo(ProtoSSLRefT *pState, uint8_t *pBuffer, int32_t iBufLen)
{
    uint32_t uHostLen = (uint32_t)strlen(pState->strHost);
    uint8_t *pBufStart = pBuffer;

    // make sure we have enough room
    if ((uHostLen+9) > (unsigned)iBufLen)
    {
        NetPrintf(("protossl: could not add extension server_name; insufficient buffer\n"));
        return(0);
    }

    // renegotiation_info extension ident
    *pBuffer++ = (uint8_t)((SSL_EXTN_RENEGOTIATION_INFO>>8)&0xff);
    *pBuffer++ = (uint8_t)((SSL_EXTN_RENEGOTIATION_INFO>>0)&0xff);
    // empty renegotiation_info length
    *pBuffer++ = (uint8_t)0;
    *pBuffer++ = (uint8_t)1;
    // empty renegotiation_info
    *pBuffer++ = (uint8_t)0;

    return((int32_t)(pBuffer-pBufStart));
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLHelloExtnWriteServerName

    \Description
        Write Server Name Indication (SNI) extension to the ClientHello handshake
        packet; ref http://tools.ietf.org/html/rfc6066#page-6

    \Input *pState      - module state reference
    \Input *pBuffer     - buffer to write extension into
    \Input iBufLen      - length of buffer

    \Output
        int32_t         - number of bytes added to buffer

    \Version 10/01/2014 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _ProtoSSLHelloExtnWriteServerName(ProtoSSLRefT *pState, uint8_t *pBuffer, int32_t iBufLen)
{
    uint32_t uHostLen = (uint32_t)strlen(pState->strHost);
    uint8_t *pBufStart = pBuffer;

    // make sure we have enough room
    if ((uHostLen+9) > (unsigned)iBufLen)
    {
        NetPrintf(("protossl: could not add extension server_name; insufficient buffer\n"));
        return(0);
    }

    // server name extension ident
    *pBuffer++ = (uint8_t)(SSL_EXTN_SERVER_NAME>>8);
    *pBuffer++ = (uint8_t)(SSL_EXTN_SERVER_NAME>>0);
    // server name extension length
    *pBuffer++ = (uint8_t)((uHostLen+5) >> 8);
    *pBuffer++ = (uint8_t)(uHostLen+5);

    // server name list length
    *pBuffer++ = (uint8_t)((uHostLen+3) >> 8);
    *pBuffer++ = (uint8_t)(uHostLen+3);
    // type: hostname
    *pBuffer++ = 0;
    // hostname length
    *pBuffer++ = (uint8_t)(uHostLen >> 8);
    *pBuffer++ = (uint8_t)(uHostLen);

    // hostname
    ds_memcpy(pBuffer, pState->strHost, uHostLen);
    pBuffer += (int32_t)uHostLen;

    return((int32_t)(pBuffer-pBufStart));
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLHelloExtnWriteSignatureAlgorithms

    \Description
        Write Signature Algorithms extension to the ClientHello handshake packet;
        ref https://tools.ietf.org/html/rfc5246#section-7.4.1.4.1

    \Input *pState      - module state reference
    \Input *pBuffer     - buffer to write extension into
    \Input iBufLen      - length of buffer

    \Output
        int32_t         - number of bytes added to buffer

    \Version 10/01/2014 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _ProtoSSLHelloExtnWriteSignatureAlgorithms(ProtoSSLRefT *pState, uint8_t *pBuffer, int32_t iBufLen)
{
    const int32_t _iListLength = sizeof(_SSL3_SignatureSchemes)/sizeof(_SSL3_SignatureSchemes[0])*2; // signature hash algorithms list length
    const int32_t _iExtnLength = 2+_iListLength; // ident+length+algorithms list length
    SecureStateT *pSecure = pState->pSecure;
    uint8_t *pBufStart = pBuffer;
    int32_t iIndex;

    // as per RFC, this extension is not meaningful for TLS versions prior to 1.2; clients MUST NOT offer it if they are offering prior versions
    if (pSecure->uSslVersion < SSL3_TLS1_2)
    {
        return(0);
    }
    // make sure we have enough room
    if ((2+2+_iExtnLength) > iBufLen)
    {
        NetPrintf(("protossl: could not add extension signature_algorithms; insufficient buffer\n"));
        return(0);
    }

    // signature hash algorithms extension ident
    *pBuffer++ = (uint8_t)(SSL_EXTN_SIGNATURE_ALGORITHMS>>8);
    *pBuffer++ = (uint8_t)(SSL_EXTN_SIGNATURE_ALGORITHMS>>0);
    // signature hash algorithms extension length
    *pBuffer++ = (uint8_t)(_iExtnLength>>8);
    *pBuffer++ = (uint8_t)(_iExtnLength>>0);
    // signature hash algorithms extension list length
    *pBuffer++ = (uint8_t)(_iListLength>>8);
    *pBuffer++ = (uint8_t)(_iListLength>>0);
    // supported signature algorithms
    for (iIndex = 0; iIndex < (signed)(sizeof(_SSL3_SignatureSchemes)/sizeof(*_SSL3_SignatureSchemes)); iIndex += 1)
    {
        *pBuffer++ = (uint8_t)(_SSL3_SignatureSchemes[iIndex].uIdent>>8);
        *pBuffer++ = (uint8_t)(_SSL3_SignatureSchemes[iIndex].uIdent>>0);
    }

    return((int32_t)(pBuffer-pBufStart));
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLHelloExtnWriteSupportedVersions

    \Description
        Write the Supported Versions extension to the ClientHello;
        ref https://tools.ietf.org/html/rfc8446#section-4.2.1

    \Input *pState      - module state reference
    \Input *pBuffer     - buffer to write extension into
    \Input iBufLen      - length of buffer

    \Output
        int32_t         - number of bytes added to buffer

    \Notes
        Unlike other extensions, supported_versions is an extension required to
        support TLS1.3.  A supported_versions extension overrides the legacy TLS
        client version in the ClientHello, which remains fixed at 1.2.

    \Version 01/24/2017 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _ProtoSSLHelloExtnWriteSupportedVersions(ProtoSSLRefT *pState, uint8_t *pBuffer, int32_t iBufLen)
{
    int32_t iVersion, iNumVersions;
    int32_t iDataLength, iExtnLength;
    uint8_t *pBufStart = pBuffer;
    SecureStateT *pSecure = pState->pSecure;

    // list all versions if not server
    if (!pState->bServer)
    {
        iNumVersions = pState->uSslVersion - pState->uSslVersionMin + 1;
        iDataLength = (iNumVersions * 2) + 1;
    }
    else
    {
        iNumVersions = 1;
        iDataLength = 2;
    }
    // calculate extension length
    iExtnLength = 2 + 2 + iDataLength;

    // make sure we have enough room
    if (iExtnLength > iBufLen)
    {
        NetPrintf(("protossl: could not add extension supported_versions; insufficient buffer\n"));
        return(0);
    }
    
    // extension ident
    *pBuffer++ = (uint8_t)(SSL_EXTN_SUPPORTED_VERSIONS>>8);
    *pBuffer++ = (uint8_t)(SSL_EXTN_SUPPORTED_VERSIONS>>0);
    // extension length
    *pBuffer++ = (uint8_t)((iDataLength)>>8);
    *pBuffer++ = (uint8_t)((iDataLength)>>0);

    // add extension data
    if (!pState->bServer)
    {
        // size of versions list
        *pBuffer++ = (uint8_t)(iNumVersions*2);
        // supported_versions data
        for (iVersion = pState->uSslVersion; iVersion >= pState->uSslVersionMin; iVersion -= 1)
        {
            *pBuffer++ = (uint8_t)(iVersion>>8);
            *pBuffer++ = (uint8_t)(iVersion>>0);
        }
    }
    else
    {
        *pBuffer++ = (uint8_t)(pSecure->uSslVersion>>8);
        *pBuffer++ = (uint8_t)(pSecure->uSslVersion>>0);
    }

    return((int32_t)(pBuffer-pBufStart));
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLAddHelloExtensions

    \Description
        Add any ClientHello extensions

    \Input *pState      - module state reference
    \Input *pBuffer     - buffer to write extension into
    \Input iBufLen      - length of buffer
    \Input *pHshkMsgBuf - handshake message buffer
    \Input uHelloExtn   - flag of which hello extensions to include

    \Output
        uint8_t *       - pointer past end of extension chunk

    \Notes
        ClientHello extension registry:
        http://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml

    \Version 10/01/2014 (jbrookes)
*/
/********************************************************************************F*/
static uint8_t *_ProtoSSLAddHelloExtensions(ProtoSSLRefT *pState, uint8_t *pBuffer, int32_t iBufLen, const uint8_t *pHshkMsgBuf, uint32_t uHelloExtn)
{
    SecureStateT *pSecure = pState->pSecure;
    uint32_t uExtnLen;
    int32_t iBufOff, iPskOff = 0;

    // skip extensions length field
    iBufOff = 2;

    // add enabled extensions (client only)
    if (!pState->bServer)
    {
        if (uHelloExtn & PROTOSSL_HELLOEXTN_SERVERNAME)
        {
            iBufOff += _ProtoSSLHelloExtnWriteServerName(pState, pBuffer+iBufOff, iBufLen-iBufOff);
        }
        if (uHelloExtn & PROTOSSL_HELLOEXTN_ELLIPTIC_CURVES)
        {
            iBufOff += _ProtoSSLHelloExtnWriteEllipticCurves(pState, pBuffer+iBufOff, iBufLen-iBufOff);
        }
    }

    // add enabled extensions (client & server)
    if (uHelloExtn & PROTOSSL_HELLOEXTN_ALPN)
    {
        iBufOff += _ProtoSSLHelloExtnWriteAlpn(pState, pBuffer+iBufOff, iBufLen-iBufOff);
    }
    if ((!pState->bServer && (pState->uSslVersionMin < SSL3_TLS1_3)) || (pState->bServer && pSecure->bRenegotiationInfo && (pSecure->uSslVersion < SSL3_TLS1_3)))
    {
        iBufOff += _ProtoSSLHelloExtnWriteRenegotiationInfo(pState, pBuffer+iBufOff, iBufLen-iBufOff);
    }
    if ((pSecure->uSslVersion >= PROTOSSL_VERSION_TLS1_3) && (pState->iState != ST3_SEND_EXTN) && (pState->iState != ST3_SEND_CERT_REQ))
    {
        iBufOff += _ProtoSSLHelloExtnWriteKeyShare(pState, pBuffer+iBufOff, iBufLen-iBufOff);
        iBufOff += _ProtoSSLHelloExtnWriteSupportedVersions(pState, pBuffer+iBufOff, iBufLen-iBufOff);
        iBufOff += _ProtoSSLHelloExtnWriteCookie(pState, pBuffer+iBufOff, iBufLen-iBufOff);
    }
    if (((uHelloExtn & PROTOSSL_HELLOEXTN_SIGALGS) && !pState->bServer) || ((pSecure->uSslVersion >= PROTOSSL_VERSION_TLS1_3) && (!pState->bServer || pState->iState == ST3_SEND_CERT_REQ)))
    {
        iBufOff += _ProtoSSLHelloExtnWriteSignatureAlgorithms(pState, pBuffer+iBufOff, iBufLen-iBufOff);
    }

    // add psk-modes (client only)
    if (!pState->bServer)
    {
        iBufOff += _ProtoSSLHelloExtnWritePreSharedKeyModes(pState, pBuffer+iBufOff, iBufLen-iBufOff);
    }

    /* get pre-shared key length so we can write overall extension length.  we need that value
       to be correct when we actually write the pre_shared_key extension, so that we can calculate
       the binder hash correctly */
    if (!pState->bServer && (pSecure->uSslVersion >= PROTOSSL_VERSION_TLS1_3))
    {
        int32_t iPskLen = _ProtoSSLHelloExtnWritePreSharedKey(pState, NULL, 0, NULL);
        iPskOff = iBufOff;
        if ((iBufOff+iPskLen) > iBufLen)
        {
            NetPrintf(("protossl: could not add extension psk_modes; insufficient buffer\n"));
            iPskLen = 0;
        }
        iBufOff += iPskLen;
    }

    // update extension length
    if (iBufOff > 2)
    {
        uExtnLen = (uint32_t)(iBufOff-2);
        pBuffer[0] = (uint8_t)(uExtnLen>>8);
        pBuffer[1] = (uint8_t)(uExtnLen>>0);
    }
    else
    {
        iBufOff = 0;
    }

    // add pre_shared_key extension
    if ((iBufOff > 2) && (iPskOff > 0))
    {
        _ProtoSSLHelloExtnWritePreSharedKey(pState, pBuffer+iPskOff, iBufLen-iPskOff, pHshkMsgBuf);
    }

    // return updated buffer pointer
    return(pBuffer+iBufOff);
}

/*
    hello extension parsing
*/

#if DIRTYCODE_LOGGING
/*F********************************************************************************/
/*!
    \Function _ProtoSSLHelloExtnGetName

    \Description
        Get debug extension name for logging

    \Input uExtnId

    \Output
        const char *    - pointer to name, or "unknown" if not recognized

    \Version 04/11/2017 (jbrookes)
*/
/********************************************************************************F*/
static const char *_ProtoSSLHelloExtnGetName(uint32_t uExtnId)
{
    const char *pName = "unknown";
    if (uExtnId <= SSL_EXTN_MAX)
    {
        pName = _SSL3_strExtensionNames[uExtnId];
    }
    else if (uExtnId == SSL_EXTN_RENEGOTIATION_INFO)
    {
        pName = "renegotiation_info";
    }
    return(pName);
}
#endif

/*F********************************************************************************/
/*!
    \Function _ProtoSSLHelloExtnParseAlpn

    \Description
        Parse the ALPN extension from the client & server hello

    \Input *pState      - module state reference
    \Input *pData       - the extension data we are parsing
    \Input *pDataEnd    - end of extension data

    \Output
        int32_t - result of opertion (zero or ST_FAIL_* on error)

    \Version 08/05/2016 (eesponda)
*/
/********************************************************************************F*/
static int32_t _ProtoSSLHelloExtnParseAlpn(ProtoSSLRefT *pState, const uint8_t *pData, const uint8_t *pDataEnd)
{
    SecureStateT *pSecure = pState->pSecure;
    uint16_t uAlpnExtensionLength = _SafeRead16(pData, pDataEnd);
    pData += 2;

    if (uAlpnExtensionLength == 0)
    {
        /* if the extension has data but the list length is zero
           treat this as handshake failure */
        NetPrintf(("protossl: received invalid alpn extension length in client hello\n"));
        _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_HANDSHAKE_FAILURE);
        return(ST_FAIL_SETUP);
    }

    if (!pState->bServer)
    {
        // save the negotiated protocol in the secure state so it can be queried (add 1 for nul terminator)
        _SafeReadString(pSecure->strAlpnProtocol, sizeof(pSecure->strAlpnProtocol), (const char *)pData+1, _SafeRead8(pData, pDataEnd), pDataEnd);
        NetPrintfVerbose((pState->iVerbose, 0, "protossl: server negotiated protocol %s using alpn extension\n", pSecure->strAlpnProtocol));
    }
    else if (pState->uNumAlpnProtocols > 0) // skip parsing if the user has not chosed any protocols
    {
        uint32_t uProtocol;

        // loop through the protocols and pick the first supported
        for (uProtocol = 0; (uProtocol < pState->uNumAlpnProtocols) && (pSecure->strAlpnProtocol[0] == '\0'); uProtocol += 1)
        {
            const AlpnProtocolT *pProtocol = &pState->aAlpnProtocols[uProtocol];
            uint32_t uOffset, uDataLen;

            for (uOffset = 0; uOffset < uAlpnExtensionLength; uOffset += uDataLen+1)
            {
                const uint8_t *pExtensionData = pData+uOffset;
                uDataLen = _SafeRead8(pExtensionData, pDataEnd);
                _SafeReadString(pSecure->strAlpnProtocol, sizeof(pSecure->strAlpnProtocol), (const char *)pExtensionData+1, uDataLen, pDataEnd);

                if (!strcmp(pSecure->strAlpnProtocol, pProtocol->strName))
                {
                    NetPrintfVerbose((pState->iVerbose, 0, "protossl: negotiated protocol %s using alpn extension\n", pSecure->strAlpnProtocol));
                    break;
                }
                else
                {
                    pSecure->strAlpnProtocol[0] = '\0';
                }
            }
        }

        if (pSecure->strAlpnProtocol[0] == '\0')
        {
            /* if we did not match a protocol then we do not support any
               of the protocols that the client prefers; treat this as handshake failure */
            NetPrintf(("protossl: client requested protocols that are not supported by the server via alpn extension\n"));
            _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_NO_APPLICATION_PROTOCOL);
            return(ST_FAIL_SETUP);
        }
    }

    return(0);
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLHelloExtnParseCookie

    \Description
        Parse the Cookie extension from the client & server hello

    \Input *pState      - module state reference
    \Input *pData       - the extension data we are parsing
    \Input *pDataEnd    - end of extension data

    \Output
        int32_t - result of opertion (zero or ST_FAIL_* on error)

    \Version 11/29/2017 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _ProtoSSLHelloExtnParseCookie(ProtoSSLRefT *pState, const uint8_t *pData, const uint8_t *pDataEnd)
{
    SecureStateT *pSecure = pState->pSecure;

    // point to cookie in receive buffer so we don't have to waste memory saving it off
    pSecure->pCookie = pData;
    NetPrintfVerbose((pState->iVerbose, 0, "protossl: parsed cookie of length %d\n", _SafeRead16(pData, pDataEnd)));

    return(0);
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLHelloExtnParseEllipticCurves

    \Description
        Parse the elliptic curves extension from the client hello

    \Input *pState      - module state reference
    \Input *pData       - the extension data we are parsing
    \Input *pDataEnd    - end of extension data

    \Output
        int32_t - result of opertion (zero or ST_FAIL_* on error)

    \Version 01/19/2017 (eesponda)
*/
/********************************************************************************F*/
static int32_t _ProtoSSLHelloExtnParseEllipticCurves(ProtoSSLRefT *pState, const uint8_t *pData, const uint8_t *pDataEnd)
{
    int32_t iEllipticCurve, iIndex;
    SecureStateT *pSecure = pState->pSecure;
    uint16_t uEllipticCurveExtensionLength = _SafeRead16(pData, pDataEnd);
    pData += 2;

    if (uEllipticCurveExtensionLength == 0)
    {
        /* if the extension has data but the list length is zero
           treat this as handshake failure */
        NetPrintf(("protossl: received invalid elliptic curves extension length in client hello\n"));
        _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_HANDSHAKE_FAILURE);
        return(ST_FAIL_SETUP);
    }

    // pick first supported elliptic curve
    for (iEllipticCurve = 0, pSecure->pEllipticCurve = NULL; (iEllipticCurve < (signed)(uEllipticCurveExtensionLength/2)) && (pSecure->pEllipticCurve == NULL); iEllipticCurve += 1, pData += 2)
    {
        for (iIndex = 0; iIndex < (signed)(sizeof(_SSL3_EllipticCurves)/sizeof(*_SSL3_EllipticCurves)); iIndex += 1)
        {
            if ((pData+2) > pDataEnd)
            {
                continue;
            }
            // skip non-matching elliptic curve
            if ((pData[0] != (uint8_t)(_SSL3_EllipticCurves[iIndex].uIdent >> 8)) || (pData[1] != (uint8_t)(_SSL3_EllipticCurves[iIndex].uIdent)))
            {
                continue;
            }
            // skip disabled curves
            if ((_SSL3_EllipticCurves[iIndex].uId & pState->uEnabledCurves) == 0)
            {
                continue;
            }
            // found an elliptic curve
            pSecure->pEllipticCurve = &_SSL3_EllipticCurves[iIndex];
            NetPrintfVerbose((pState->iVerbose, 0, "protossl: using elliptic curve %s (ident=0x%04x)\n", pSecure->pEllipticCurve->strName, pSecure->pEllipticCurve->uIdent));
            break;
        }
    }

    // ecdhe key exchange is required in TLS1.3+. if we cannot find a curve that we share in common, we need to fail the handshake
    if ((pSecure->uSslVersion >= PROTOSSL_VERSION_TLS1_3) && (pSecure->pEllipticCurve == NULL))
    {
        NetPrintf(("protossl: no elliptic curve found\n"));
        _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_HANDSHAKE_FAILURE);
        return(ST_FAIL_SETUP);
    }

    return(0);
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLHelloExtnParseKeyShare

    \Description
        Parse the key share extension from the hello (tls1.3)

    \Input *pState      - module state reference
    \Input *pData       - the extension data we are parsing
    \Input *pDataEnd    - end of extension data

    \Output
        int32_t - result of opertion (zero or ST_FAIL_* on error)

    \Version 01/25/2017 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _ProtoSSLHelloExtnParseKeyShare(ProtoSSLRefT *pState, const uint8_t *pData, const uint8_t *pDataEnd)
{
    SecureStateT *pSecure = pState->pSecure;
    int32_t iKeyShare, iIndex;
    uint16_t uKeyShareCurveId;
    uint32_t uKeyShareLen;
    uint32_t uExtnLen;

    NetPrintfVerbose((pState->iVerbose, 0, "protossl: parsing keyshare extension\n"));

    // process extension length; server only
    if (pState->bServer)
    {
        if ((uExtnLen = _SafeRead16(pData, pDataEnd)) == 0)
        {
            NetPrintf(("protossl: received empty key_share\n"));
        }
        pDataEnd = DS_MIN(&pData[2]+uExtnLen, pDataEnd);
        pData += 2;
    }

    // iterate through array of key share objects
    for (iKeyShare = 0, uKeyShareLen = 0, pSecure->pEllipticCurve = NULL; (pData < pDataEnd) && (pSecure->pEllipticCurve == NULL); iKeyShare += 1)
    {
        // get keyshare object curve id
        uKeyShareCurveId = _SafeRead16(pData, pDataEnd);
        pData += 2;
        // get keyshare size (if key is present)
        if (pData < pDataEnd)
        {
            uKeyShareLen = _SafeRead16(pData, pDataEnd);
            // skip to keyshare data
            pData += 2;
        }

        // see if we have a match
        for (iIndex = 0; iIndex < (signed)(sizeof(_SSL3_EllipticCurves)/sizeof(*_SSL3_EllipticCurves)); iIndex += 1)
        {
            // skip non-matching elliptic curve
            if (uKeyShareCurveId != _SSL3_EllipticCurves[iIndex].uIdent)
            {
                continue;
            }
            // skip disabled curves
            if ((_SSL3_EllipticCurves[iIndex].uId & pState->uEnabledCurves) == 0)
            {
                continue;
            }
            // found an elliptic curve
            pSecure->pEllipticCurve = &_SSL3_EllipticCurves[iIndex];

            // save key
            if (uKeyShareLen > 0)
            {
                _SafeReadBytes(pSecure->PubKey, sizeof(pSecure->PubKey), pData, uKeyShareLen, pDataEnd);
                pSecure->uPubKeyLength = uKeyShareLen;
            }

            NetPrintfVerbose((pState->iVerbose, 0, "protossl: using elliptic curve %s (ident=0x%04x)\n", pSecure->pEllipticCurve->strName, pSecure->pEllipticCurve->uIdent));
            #if DEBUG_RAW_DATA
            NetPrintMem(pSecure->PubKey, pSecure->uPubKeyLength, "key_share");
            #endif
            break;
        }
        // move to next keyshare object
        pData += uKeyShareLen;
    }

    return(0);
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLHelloExtnParsePreSharedKey

    \Description
        Parse the pre_shared_key extension from the hello (tls1.3)

    \Input *pState      - module state reference
    \Input *pData       - the extension data we are parsing
    \Input *pDataEnd    - end of extension data

    \Output
        int32_t     - result of opertion (zero or ST_FAIL_* on error)

    \Version 12/13/2017 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _ProtoSSLHelloExtnParsePreSharedKey(ProtoSSLRefT *pState, const uint8_t *pData, const uint8_t *pDataEnd)
{
    SecureStateT *pSecure = pState->pSecure;

    NetPrintfVerbose((pState->iVerbose, 0, "protossl: parsing pre_shared_key extension\n"));

    if (pState->bServer)
    {
        //$$TODO add server parsing of psk
    }
    else
    {
        uint16_t uIdentity = _SafeRead16(pData, pDataEnd);
        pSecure->bSessionResume = (uIdentity == 0) ? TRUE : FALSE;
        NetPrintf(("protossl: psk selected identity=%d; resume %s\n", uIdentity, pSecure->bSessionResume ? "true" : "false"));
    }

    return(0);
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLHelloExtnParseRenegotiationInfo

    \Description
        Parse the Renegotiation Info extension from the client & server hello
        ref https://tools.ietf.org/html/rfc5746

    \Input *pState      - module state reference
    \Input *pData       - the extension data we are parsing
    \Input *pDataEnd    - end of extension data

    \Output
        int32_t - result of opertion (zero or ST_FAIL_* on error)

    \Version 03/27/2018 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _ProtoSSLHelloExtnParseRenegotiationInfo(ProtoSSLRefT *pState, const uint8_t *pData, const uint8_t *pDataEnd)
{
    SecureStateT *pSecure = pState->pSecure;
    pSecure->bRenegotiationInfo = TRUE;
    NetPrintfVerbose((pState->iVerbose, 0, "protossl: parsed renegotation_info\n"));
    return(0);
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLHelloExtnParseSignatureAlgorithms

    \Description
        Parse the signature algorithms (tls1.3 calls them signature
        schemes) extension.

    \Input *pState      - module state reference
    \Input *pData       - the extension data we are parsing
    \Input *pDataEnd    - end of extension data

    \Output
        int32_t - result of opertion (zero or ST_FAIL_* on error)

    \Notes
        This is needed when sending ServerKeyExchange messages to the client

    \Version 03/03/2017 (eesponda)
*/
/********************************************************************************F*/
static int32_t _ProtoSSLHelloExtnParseSignatureAlgorithms(ProtoSSLRefT *pState, const uint8_t *pData, const uint8_t *pDataEnd)
{
    SecureStateT *pSecure = pState->pSecure;
    uint16_t uSigSchemeLen;

    // get sig scheme length
    uSigSchemeLen = _SafeRead16(pData, pDataEnd);
    pData += 2;

    // if the extension has data but the list length is zero treat this as handshake failure
    if (uSigSchemeLen == 0)
    {
        NetPrintf(("protossl: received invalid signature algorithms length in client hello\n"));
        _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_HANDSHAKE_FAILURE);
        return(ST_FAIL_SETUP);
    }

    // pick first supported signature algorithm
    pSecure->pSigScheme = _ProtoSSLChooseSignatureScheme(pSecure, pState->pCertificate, pData, uSigSchemeLen, pDataEnd);

    /* tls1.2 https://tools.ietf.org/html/rfc5246#section-7.4.1.4.1) and tls1.3 (https://tools.ietf.org/html/rfc8446#section-9.2)
       define actions around the presence/absence of this extension, but do not define actions in the case no suitable signature algorithms 
       are included.  we treat this as a handshake failure based around observed behavior of other implementations e.g. openssl */
    if (pSecure->pSigScheme == NULL)
    {
        NetPrintf(("protossl: no acceptable signature algorithms included in signature_algorithms extension\n"));
        _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_HANDSHAKE_FAILURE);
        return(ST_FAIL_SETUP);
    }

    // log choice and return success
    NetPrintfVerbose((pState->iVerbose, 0, "protossl: using signature scheme 0x%04x\n", pSecure->pSigScheme->uIdent));
    return(0);
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLHelloExtnParseSupportedVersions

    \Description
        Parse the Supported Versions extension from the ClientHello
        ref https://tools.ietf.org/html/rfc8446#section-4.2.1

    \Input *pState      - module state reference
    \Input *pData       - the extension data we are parsing
    \Input *pDataEnd    - end of extension data

    \Output
        int32_t - result of opertion (zero or ST_FAIL_* on error)

    \Version 01/26/2017 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _ProtoSSLHelloExtnParseSupportedVersions(ProtoSSLRefT *pState, const uint8_t *pData, const uint8_t *pDataEnd)
{
    SecureStateT *pSecure = pState->pSecure;
    uint32_t uExtnLength = pState->bServer ? _SafeRead8(pData++, pDataEnd) : 2;
    uint32_t uSslVersion;

    // pick first valid supported version
    for (pDataEnd = pData + uExtnLength; pData < pDataEnd; pData += 2)
    {
        uSslVersion = _SafeRead16(pData, pDataEnd);

        // range check
        if ((uSslVersion < pState->uSslVersionMin) || (uSslVersion > pState->uSslVersion))
        {
            continue;
        }

        // found a version, roll with it
        pSecure->uSslClientVersion = pSecure->uSslVersion = uSslVersion;
        NetPrintfVerbose((pState->iVerbose, 0, "protossl: ssl protocol version overriden by supported_versions extension to %s\n", _SSL3_strVersionNames[pSecure->uSslVersion & 0xff]));
        return(0);
    }

    /* if the "supported_versions" extension in the ServerHello contains a version not offered by the client or contains
       a version prior to TLS 1.3, the client MUST abort the handshake with an "illegal_parameter" alert */
    if (!pState->bServer)
    {
        NetPrintf(("protossl: supported_versions extension did not contain a valid version\n"));
        _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_ILLEGAL_PARAMETER);
        return(ST_FAIL_SETUP);
    }
    return(0);
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLParseHelloExtensions

    \Description
        Parse the hello extensions in the client & server hello handshake packets

    \Input *pState              - module state reference
    \Input *pData               - start of extension data (length word)
    \Input *pDataEnd            - end of the payload data
    \Input uExtensionParse      - id of extension to parse, or -1 to parse all

    \Output
        int32_t - result of opertion (zero or ST_FAIL_* on error)

    \Version 08/05/2016 (eesponda)
*/
/********************************************************************************F*/
static int32_t _ProtoSSLParseHelloExtensions(ProtoSSLRefT *pState, const uint8_t *pData, const uint8_t *pDataEnd, uint16_t uExtensionParse)
{
    SecureStateT *pSecure = pState->pSecure;
    uint16_t uExtensionType, uExtensionLength;
    int32_t iResult = 0;

    // read overall length of extensions, and constrain reading to extension end
    uExtensionLength = _SafeRead16(pData, pDataEnd);
    pDataEnd = DS_MIN(&pData[2]+uExtensionLength, pDataEnd);
    pData += 2;

    // parse the complete extension section while we have not encountered an error
    for (iResult = 0; (pData < pDataEnd) && (iResult == 0); pData += uExtensionLength)
    {
        // get extension type and length
        uExtensionType = _SafeRead16(pData, pDataEnd);
        uExtensionLength = _SafeRead16(pData+2, pDataEnd);
        // skip header
        pData += 4;

        // validate extension fits in available space
        if ((pData+uExtensionLength) > pDataEnd)
        {
            NetPrintf(("protossl: extension length %d too big\n", uExtensionLength));
            _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_DECODE_ERROR);
            return(ST_FAIL_SETUP);
        }

        // check for specific extension parsing
        if ((uExtensionParse != SSL_EXTN_ALL) && (uExtensionParse != uExtensionType))
        {
            continue;
        }

        NetPrintfVerbose((DEBUG_MSG_LIST, 0, "protossl: parsing extension %s (%d) with length %d\n", _ProtoSSLHelloExtnGetName(uExtensionType), uExtensionType, uExtensionLength));

        // skip extensions without data
        if (uExtensionLength == 0)
        {
            continue;
        }

        #if DEBUG_RAW_DATA
        NetPrintMem(pData, uExtensionLength, "extension data");
        #endif

        // parse extensions only clients send
        if (pState->bServer)
        {
            if (uExtensionType == SSL_EXTN_ELLIPTIC_CURVES)
            {
                iResult = _ProtoSSLHelloExtnParseEllipticCurves(pState, pData, pDataEnd);
            }
        }

        // parse ALPN extension
        if (uExtensionType == SSL_EXTN_ALPN)
        {
            iResult = _ProtoSSLHelloExtnParseAlpn(pState, pData, pDataEnd);
        }

        // the following extensions are only parsed if this is a tls1.2 or greater connection request
        if (pSecure->uSslVersion < SSL3_TLS1_2)
        {
            continue;
        }

        // parse supported_versions extension only if tls1.3 is enabled, hello version is 1.2, and we are explicitly parsing for it
        if ((pState->uSslVersion >= SSL3_TLS1_3) && (pSecure->uSslVersion == SSL3_TLS1_2) && (uExtensionType == SSL_EXTN_SUPPORTED_VERSIONS) && (uExtensionParse == uExtensionType))
        {
            iResult = _ProtoSSLHelloExtnParseSupportedVersions(pState, pData, pDataEnd);
        }

        // parse SignatureAlgorithms
        if (uExtensionType == SSL_EXTN_SIGNATURE_ALGORITHMS)
        {
            iResult = _ProtoSSLHelloExtnParseSignatureAlgorithms(pState, pData, pDataEnd);
        }
        if (uExtensionType == SSL_EXTN_RENEGOTIATION_INFO)
        {
            iResult = _ProtoSSLHelloExtnParseRenegotiationInfo(pState, pData, pDataEnd);
        }

        // the following extensions are only parsed if this is a tls1.3 connection request
        if (pSecure->uSslVersion < SSL3_TLS1_3)
        {
            continue;
        }

        // parse cookie extension
        if (uExtensionType == SSL_EXTN_COOKIE)
        {
            iResult = _ProtoSSLHelloExtnParseCookie(pState, pData, pDataEnd);
        }
        // parse KeyShare extnesion
        if (uExtensionType == SSL_EXTN_KEY_SHARE)
        {
            iResult = _ProtoSSLHelloExtnParseKeyShare(pState, pData, pDataEnd);
        }
        // parse PreSharedKey extnesion
        if (uExtensionType == SSL_EXTN_PRE_SHARED_KEY)
        {
            iResult = _ProtoSSLHelloExtnParsePreSharedKey(pState, pData, pDataEnd);
        }
    }

    return(iResult);
}

/*
    handshaking
*/

/*F********************************************************************************/
/*!
    \Function _ProtoSSLSendClientHello

    \Description
        Send ClientHello handshake packet; ref http://tools.ietf.org/html/rfc5246#section-7.4.1.2
        for TLS1.2 and https://tools.ietf.org/html/rfc8446#section-4.1.2 for TLS1.3

    \Input *pState      - module state reference

    \Output
        int32_t         - next state (ST3_SEND_HELLO, ST3_RECV_HELLO, ST_FAIL_SETUP)

    \Version 03/15/2013 (jbrookes) Added session resume support
*/
/********************************************************************************F*/
static int32_t _ProtoSSLSendClientHello(ProtoSSLRefT *pState)
{
    int32_t iCipher, iNumCiphers, iBodyLen;
    uint8_t strHead[4], strBody[2048];
    uint8_t *pData = strBody, *pCiphSize;
    SecureStateT *pSecure = pState->pSecure;
    SessionHistoryT SessHist;
    uint32_t uHelloExtn = pState->uHelloExtn, uEnabledCiphers = pState->uEnabledCiphers;

    // if we haven't picked an elliptic curve, pick our default
    if ((pSecure->pEllipticCurve == NULL) && (pState->iCurveDflt != -1))
    {
        pSecure->pEllipticCurve = _ProtoSSLChooseCurve(pState->uEnabledCurves, pState->iCurveDflt);
    }

    // for tls1.3 start ecdhe key generation here so we can spread it out
    if ((pState->uSslVersion >= PROTOSSL_VERSION_TLS1_3) && (pSecure->pEllipticCurve != NULL))
    {
        const CryptCurveDhT *pEcc;
        uint32_t uCryptUsecs;

        // initialize elliptic curve context if not already initialized
        if ((pEcc = _ProtoSSLEccInitContext(pSecure, pSecure->pEllipticCurve)) == NULL)
        {
            /* if we cannot find the dh functions, there is some configuration mishap or the server is faulty.
               let's fail early here so we can debug the issue instead of a null pointer exception */
            return(ST_FAIL_SETUP);
        }
        // generate the public key
        if (pEcc->Public(pSecure->EccContext, NULL, &uCryptUsecs) > 0)
        {
            return(ST3_SEND_HELLO);
        }
        pSecure->uPubKeyLength = pEcc->PointFinal(pSecure->EccContext, NULL, FALSE, pSecure->PubKey, sizeof(pSecure->PubKey));
        pSecure->bEccKeyGenerated = TRUE;
        NetPrintfVerbose((DEBUG_ENC_PERF, 0, "protossl: SSL Perf (generate public key for server key message) %dms\n",
            uCryptUsecs/1000));
        pSecure->uTimer += uCryptUsecs/1000;
    }

    NetPrintfVerbose((DEBUG_MSG_LIST, 0, "protossl: SSL Msg: Send ClientHello\n"));

    // initialize the ssl performance timer
    pSecure->uTimer = 0;

    // reset client cert level (server will let us know if we need to send one)
    pState->iClientCertLevel = 0;

    // set desired ssl version
    pSecure->uSslVersion = pState->uSslVersion;
    // remember requested version; this is later used to detect a version rollback attack
    pSecure->uSslClientVersion = pState->uSslVersion;

    // set TLS1.3 specific options
    if (pSecure->uSslClientVersion >= PROTOSSL_VERSION_TLS1_3)
    {
        // freeze hello version at 1.2 as per https://tools.ietf.org/html/rfc8446#section-4.1.2
        pSecure->uSslClientVersion = PROTOSSL_VERSION_TLS1_2;
        // add required elliptic_curves (which TLS1.3 calls supported_groups)
        uHelloExtn |= PROTOSSL_HELLOEXTN_ELLIPTIC_CURVES;
        // if no tls1.3 ciphers are enabled, do that now
        if ((pState->uSslVersion >= PROTOSSL_VERSION_TLS1_3) && !(uEnabledCiphers & PROTOSSL_CIPHER_ALL_13))
        {
            uEnabledCiphers |= PROTOSSL_CIPHER_ALL_13;
        }
    }
    *pData++ = (uint8_t)(pSecure->uSslClientVersion>>8);
    *pData++ = (uint8_t)(pSecure->uSslClientVersion>>0);

    // set random data
    if (pState->uSslVersion < PROTOSSL_VERSION_TLS1_3)
    {
        // TLS1.2 and prior specify first four bytes of client random are utc time
        uint32_t uUtcTime = (uint32_t)ds_timeinsecs();
        pSecure->ClientRandom[0] = (uint8_t)(uUtcTime >> 24);
        pSecure->ClientRandom[1] = (uint8_t)(uUtcTime >> 16);
        pSecure->ClientRandom[2] = (uint8_t)(uUtcTime >> 8);
        pSecure->ClientRandom[3] = (uint8_t)(uUtcTime >> 0);
        CryptRandGet(&pSecure->ClientRandom[4], sizeof(pSecure->ClientRandom) - 4);
    }
    else
    {
        // TLS1.3 has 32 bytes of random data
        CryptRandGet(pSecure->ClientRandom, sizeof(pSecure->ClientRandom));
    }
    #if DEBUG_RAW_DATA
    NetPrintMem(pSecure->ClientRandom, sizeof(pSecure->ClientRandom), "ClientRandom");
    #endif
    // set client random in packet
    ds_memcpy(pData, pSecure->ClientRandom, sizeof(pSecure->ClientRandom));
    pData += 32;

    // if we have a previous session for this peer and resume is enabled, set it
    if ((_SessionHistoryGetInfo(&SessHist, pState->strHost, SockaddrInGetPort(&pState->PeerAddr), NULL) != NULL) && pState->bSessionResumeEnabled)
    {
        SessionInfoT *pSessInfo = &SessHist.SessionInfo;
        NetPrintfVerbose((DEBUG_RES_SESS, 0, "protossl: setting session id for resume\n"));
        #if DEBUG_RES_SESS && DEBUG_RAW_DATA
        NetPrintMem(pSessInfo->SessionId, sizeof(pSessInfo->SessionId), "ClientHello session id");
        #endif
        /* save session id and master secret; the sessionid will be compared when we
           receive the serverhello to determine if we are in the resume flow or not */
        ds_memcpy(pSecure->SessionId, pSessInfo->SessionId, sizeof(pSecure->SessionId));
        ds_memcpy(pSecure->MasterKey, pSessInfo->MasterSecret, sizeof(pSecure->MasterKey));
        pSecure->bSentSessionId = TRUE;
    }
    else if (pSecure->uSslVersion >= PROTOSSL_VERSION_TLS1_3)
    {
        /* as per https://tools.ietf.org/html/rfc8446#section-4.1.2 a tls 1.3 client MUST always send
           a non-empty sessionID in support of middlebox compatibility mode */
        if (!pSecure->bSentSessionId)
        {
            /* generate a new session id if we haven't sent one already; otherwise, in an HRR flow we
               need to resend the same sessionid as we did the first time */
            CryptRandGet(pSecure->SessionId, sizeof(pSecure->SessionId));
            pSecure->bSentSessionId = TRUE;
        }
    }
    else
    {
        pSecure->bSentSessionId = FALSE;
    }

    // add sessionid to ClientHello
    if (pSecure->bSentSessionId)
    {
        *pData++ = (uint8_t)sizeof(pSecure->SessionId);
        ds_memcpy(pData, pSecure->SessionId, sizeof(pSecure->SessionId));
        pData += sizeof(pSecure->SessionId);
    }
    else // add in empty session identifier
    {
        *pData++ = 0;
    }

    // add the cipher suite list
    *pData++ = 0;
    pCiphSize = pData++; // save and skip cipher size location
    for (iCipher = 0, iNumCiphers = 0, pSecure->uSentCiphers = 0; iCipher < (signed)(sizeof(_SSL3_CipherSuite)/sizeof(_SSL3_CipherSuite[0])); iCipher += 1)
    {
        // skip ciphers that require a newer version of SSL than we are offering
        if (_SSL3_CipherSuite[iCipher].uMinVers > pState->uSslVersion)
        {
            continue;
        }
        // skip disabled ciphers
        if ((_SSL3_CipherSuite[iCipher].uId & uEnabledCiphers) == 0)
        {
            continue;
        }
        // add cipher to list
        *pData++ = (uint8_t)(_SSL3_CipherSuite[iCipher].uIdent>>8);
        *pData++ = (uint8_t)(_SSL3_CipherSuite[iCipher].uIdent>>0);
        iNumCiphers += 1;
        // remember we sent it
        pSecure->uSentCiphers |= _SSL3_CipherSuite[iCipher].uId;
        // add the elliptic curve extension if we are using any ecdhe ciphers
        if (_SSL3_CipherSuite[iCipher].uKey == SSL3_KEY_ECDHE)
        {
            uHelloExtn |= PROTOSSL_HELLOEXTN_ELLIPTIC_CURVES;
        }
    }
    // make sure we selected at least one cipher
    if (iNumCiphers == 0)
    {
        NetPrintf(("protossl: no ciphers selected in ClientHello\n"));
        return(ST_FAIL_SETUP);
    }
    // write cipher suite list size
    *pCiphSize = iNumCiphers*2;

    // add compression_methods list with only null compression (we don't support compression)
    *pData++ = 1;
    *pData++ = 0;

    // add extensions
    if (uHelloExtn != 0)
    {
        pData = _ProtoSSLAddHelloExtensions(pState, pData, (uint32_t)sizeof(strBody)-(uint32_t)(pData-strBody), strBody, uHelloExtn);
    }

    // setup the header
    iBodyLen = pData-strBody;
    strHead[0] = SSL3_MSG_CLIENT_HELLO;
    strHead[1] = 0;
    strHead[2] = (uint8_t)(iBodyLen>>8);
    strHead[3] = (uint8_t)(iBodyLen>>0);

    // if in hrr flow, make sure we're not sending the same clienthello we sent last time
    if (pSecure->uSslVersion >= SSL3_TLS1_3)
    {
        uint8_t aClientHelloHash[sizeof(pSecure->aClientHelloHash)];
        // hash clienthello for possible hrr compare
        _ProtoSSLGenerateFingerprint(aClientHelloHash, sizeof(aClientHelloHash), strBody, iBodyLen, CRYPTHASH_SHA256);
        // if in hrr flow, compare
        if ((pSecure->bHelloRetry) && (!memcmp(aClientHelloHash, pSecure->aClientHelloHash, sizeof(aClientHelloHash))))
        {
            /* as per https://tools.ietf.org/html/rfc8446#section-4.1.4 sending the same clienthello twice
               must generate an illegal_parameter alert */
            NetPrintf(("protossl: sending the same ClientHello twice\n"));
            _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_ILLEGAL_PARAMETER);
            return(ST_FAIL_SETUP);

        }
        // copy hello hash to state for possible future compare
        ds_memcpy_s(pSecure->aClientHelloHash, sizeof(pSecure->aClientHelloHash), aClientHelloHash, sizeof(aClientHelloHash));
    }

    // set starting handshake message
    pSecure->iCurMsg = SSL3_MSG_CLIENT_HELLO;

    // setup packet for send
    _ProtoSSLSendPacket(pState, SSL3_REC_HANDSHAKE, strHead, 4, strBody, iBodyLen);
    return(ST3_RECV_HELLO);
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLRecvClientHello

    \Description
        Process ClientHello handshake packet

    \Input *pState      - module state reference
    \Input *pData       - packet data
    \Input iDataSize    - size of packet data

    \Output
        int32_t         - next state (ST3_SEND_HELLO, ST3_SEND_HELLO_RETRY, or ST_FAIL_* on error)

    \Version 10/15/2013 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _ProtoSSLRecvClientHello(ProtoSSLRefT *pState, const uint8_t *pData, int32_t iDataSize)
{
    SecureStateT *pSecure = pState->pSecure;
    int32_t iNumCiphers, iParseResult;
    const uint8_t *pDataEnd = pData + iDataSize;
    const uint8_t *pCipherList;
    SessionHistoryT SessHist;
    uint32_t uEnabledCiphers = pState->uEnabledCiphers, uPreferredCipher=0, uSessionSize;
    uint8_t aSessionId[SSL_SESSID_SIZE], uCompression, uCompressionMethods;

    NetPrintfVerbose((DEBUG_MSG_LIST, 0, "protossl: SSL Msg: Process ClientHello\n"));

    // get protocol version client wants from us; make sure it is at most TLS1.2, as that is the highest allowed version for this field
    if ((pSecure->uSslClientVersion = pSecure->uSslVersion = _SafeRead16(pData, pDataEnd)) > PROTOSSL_VERSION_TLS1_2)
    {
        NetPrintf(("protossl: invalid ssl version %d in client hello\n", pSecure->uSslClientVersion));
        _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_ILLEGAL_PARAMETER);
        return(ST_FAIL_CONN);
    }
    pData += 2;

    // save client random data
    _SafeReadBytes(pSecure->ClientRandom, sizeof(pSecure->ClientRandom), pData, sizeof(pSecure->ClientRandom), pDataEnd);
    #if DEBUG_RAW_DATA
    NetPrintMem(pSecure->ClientRandom, sizeof(pSecure->ClientRandom), "ClientRandom");
    #endif
    pData += 32;

    // check for possible session resume
    uSessionSize = _SafeRead8(pData++, pDataEnd);
    _SafeReadBytes(aSessionId, sizeof(aSessionId), pData, SSL_SESSID_SIZE, pDataEnd);
    pData += uSessionSize;

    // read cipher suite list size and save cipher list pointer (we will parse it after extensions, if present)
    iNumCiphers = _SafeRead16(pData, pDataEnd) / 2;
    pData += 2;
    pCipherList = pData;
    // skip cipher list
    pData += iNumCiphers*2;

    // save and skip compression_methods
    uCompressionMethods = _SafeRead8(pData++, pDataEnd);
    // read first method; allows us to later check for one null method if we have a tls1.3 connection
    uCompression = _SafeRead8(pData, pDataEnd);
    pData += uCompressionMethods;

    // parse hello extensions for supported_versions first; this allows extension parsers to know if the connection will be tls1.3 or not
    if ((iParseResult = _ProtoSSLParseHelloExtensions(pState, pData, pDataEnd, SSL_EXTN_SUPPORTED_VERSIONS)) != 0)
    {
        return(iParseResult);
    }

    // pick protocol version; this must be done after possible supported_versions extension parsing
    if (pSecure->uSslVersion > pState->uSslVersion)
    {
        NetPrintfVerbose((pState->iVerbose, 1, "protossl: client requested SSL version %d.%d, downgrading to our max supported version\n", pData[0], pData[1]));
        pSecure->uSslVersion = pState->uSslVersion;
    }
    else if (pSecure->uSslVersion < pState->uSslVersionMin)
    {
        NetPrintf(("protossl: client requested SSL version %d.%d is not supported\n", pData[0], pData[1]));
        /* As per http://tools.ietf.org/html/rfc5246#appendix-E.1, if server supports (or is willing to use)
           only versions greater than client_version, it MUST send a "protocol_version" alert message and
           close the connection. */
        _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_PROTOCOL_VERSION);
        return(ST_FAIL_CONN_MINVERS);
    }
    NetPrintfVerbose((pState->iVerbose, 0, "protossl: using %s\n", _SSL3_strVersionNames[pSecure->uSslVersion&0xff]));

    // parse all other extensions
    if ((iParseResult = _ProtoSSLParseHelloExtensions(pState, pData, pDataEnd, SSL_EXTN_ALL)) != 0)
    {
        return(iParseResult);
    }

    // check for session resume info
    if (pSecure->uSslVersion < PROTOSSL_VERSION_TLS1_3)
    {
        if (pState->bSessionResumeEnabled)
        {
            // get info for possible resume
            if ((uSessionSize == sizeof(pSecure->SessionId)) && _SessionHistoryGetInfo(&SessHist, NULL, 0, aSessionId))
            {
                // flag that we got the info
                pSecure->bSessionResume = TRUE;
                // save cipher preference
                uPreferredCipher = SessHist.SessionInfo.uCipherId;
            }
        }
    }
    else
    {
        // save session info for later echo as per https://tools.ietf.org/html/rfc8446#section-4.1.3
        if (uSessionSize == sizeof(pSecure->SessionId))
        {
            ds_memcpy(pSecure->SessionId, aSessionId, sizeof(pSecure->SessionId));
            pSecure->bSessionResume = TRUE;
        }
        else
        {
            pSecure->bSessionResume = FALSE;
        }
        // if we are in the HelloRetryRequest flow, set the preferred cipher to the cipher we previously selected
        if (pSecure->bHelloRetry)
        {
            uPreferredCipher = pSecure->uRetryCipher;
        }
    }

    // choose a cipher from cipher list, with optional cipher preference
    if ((pSecure->pCipher = _ProtoSSLChooseCipher(pSecure, pState->pCertificate, pCipherList, iNumCiphers, pDataEnd, uEnabledCiphers, uPreferredCipher)) == NULL)
    {
        NetPrintf(("protossl: no matching cipher\n"));
        /* As per http://tools.ietf.org/html/rfc5246#section-7.4.1.3, a client providing no
           ciphers we support results in a handshake failure alert */
        _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_HANDSHAKE_FAILURE);
        return(ST_FAIL_CONN_NOCIPHER);
    }
    else
    {
        NetPrintfVerbose((pState->iVerbose, 0, "protossl: using cipher suite %s (ident=%d,%d)\n", pSecure->pCipher->strName, pCipherList[0], pCipherList[1]));
    }

    // make sure legacy_compression field includes only null compression if tls 1.3
    if ((pSecure->uSslVersion >= PROTOSSL_VERSION_TLS1_3) && ((uCompressionMethods != 1) || (uCompression != 0)))
    {
        /* as per https://tools.ietf.org/html/rfc8446#section-4.1.2 compression must be disabled; "if a TLS 1.3 ClientHello is
           received with any other value in this field, the server MUST abort the handshake with an "illegal_parameter" alert */
        NetPrintf(("protossl: legacy_compression_methods not null for tls 1.3 connection\n"));
        _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_ILLEGAL_PARAMETER);
        return(ST_FAIL_CONN);
    }

    // now that we've selected the cipher, check for possible resume
    if ((pSecure->uSslVersion < SSL3_TLS1_3) && (pSecure->bSessionResume))
    {
        SessionInfoT *pSessInfo = &SessHist.SessionInfo;
        if ((pSessInfo->uSslVersion == pSecure->uSslVersion) && (pSessInfo->uCipherId == pSecure->pCipher->uIdent))
        {
            ds_memcpy(pSecure->SessionId, aSessionId, sizeof(pSecure->SessionId));
            #if DEBUG_RAW_DATA
            NetPrintMem(pSecure->SessionId, sizeof(pSecure->SessionId), "ClientHello session id");
            #endif
            NetPrintfVerbose((pState->iVerbose, 0, "protossl: resuming previous session\n"));
            // copy session id and master secret
            ds_memcpy(pSecure->MasterKey, pSessInfo->MasterSecret, sizeof(pSecure->MasterKey));
            ds_memcpy(pSecure->SessionId, pSessInfo->SessionId, sizeof(pSecure->SessionId));
        }
        else
        {
            NetPrintfVerbose((pState->iVerbose, 0, "protossl: no session resume due to cipher or version mismatch\n"));
            pSecure->bSessionResume = FALSE;
        }
    }

    // if no session resume, generate a new sessionid for possible future reuse
    if ((pSecure->uSslVersion < SSL3_TLS1_3) && !pSecure->bSessionResume)
    {
        CryptRandGet(pSecure->SessionId, sizeof(pSecure->SessionId));
    }

    // make sure we found a key share we support
    if ((pSecure->uSslVersion >= PROTOSSL_VERSION_TLS1_3) && ((pSecure->pEllipticCurve == NULL) || (pSecure->uPubKeyLength == 0)))
    {
        // pick default key share
        pSecure->pEllipticCurve = _ProtoSSLChooseCurve(pState->uEnabledCurves, pState->iCurveDflt);
        return(ST3_SEND_HELLO_RETRY);
    }

    // if not tls1.3 and we got a key_share from a 1.3 client, zero it here so we don't think it's a valid key later in the handshake flow
    if ((pSecure->uSslVersion < SSL3_TLS1_3) && (pSecure->uPubKeyLength != 0))
    {
        pSecure->uPubKeyLength = 0;
    }

    return(ST3_SEND_HELLO);
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLSendHelloRetryRequest

    \Description
        Send Hello Retry Request (TLS 1.3+) as defined by
        https://tools.ietf.org/html/rfc8446#section-4.1.4

    \Input *pState      - module state reference

    \Output
        int32_t         - ST3_RECV_HELLO

    \Version 08/08/2017 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _ProtoSSLSendHelloRetryRequest(ProtoSSLRefT *pState)
{
    uint8_t strBody[512], *pData = strBody;
    uint8_t strHead[4];
    uint16_t uSslVersion;
    SecureStateT *pSecure = pState->pSecure;

    NetPrintfVerbose((DEBUG_MSG_LIST, 0, "protossl: SSL Msg: Send HelloRetryRequest\n"));

    // remember we're in HRR flow
    pSecure->bHelloRetry = TRUE;

    // inject synthetic message hash (we only need to do tls1.3 hashes here)
    _ProtoSSLHandshakeHashInject(pSecure, CRYPTHASH_SHA256);
    _ProtoSSLHandshakeHashInject(pSecure, CRYPTHASH_SHA384);

    // add fixed tls1.2 version to message body
    uSslVersion = SSL3_TLS1_2;
    *pData++ = (uint8_t)(uSslVersion>>8);
    *pData++ = (uint8_t)(uSslVersion>>0);

    // add special random value to indicate this is HelloRetryRequest, not a ServerHello
    ds_memcpy(pData, _SSL3_HelloRetryRequestRandom, sizeof(_SSL3_HelloRetryRequestRandom));
    pData += 32;
    // echo client's sessionid
    if (pSecure->bSessionResume)
    {
        *pData++ = sizeof(pSecure->SessionId);
        ds_memcpy(pData, pSecure->SessionId, sizeof(pSecure->SessionId));
        pData += sizeof(pSecure->SessionId);
    }
    else
    {
        *pData++ = 0;
    }

    // the cipher we send here MUST be chosen in the subsequent ServerHello
    pSecure->uRetryCipher = pSecure->pCipher->uIdent;
    *pData++ = (uint8_t)(pSecure->uRetryCipher>>8);
    *pData++ = (uint8_t)(pSecure->uRetryCipher>>0);

    // set legacy_compression_method
    *pData++ = 0;

    // add keyshare extension
    pData = _ProtoSSLAddHelloExtensions(pState, pData, (uint32_t)(sizeof(strBody)-(pData-strBody)), strBody, 0);

    // setup the header
    strHead[0] = SSL3_MSG_SERVER_HELLO;
    strHead[1] = 0;
    strHead[2] = (uint8_t)((pData-strBody)>>8);
    strHead[3] = (uint8_t)((pData-strBody)>>0);

    // setup packet for send
    _ProtoSSLSendPacket(pState, SSL3_REC_HANDSHAKE, strHead, 4, strBody, pData-strBody);

    // return next state
    return(ST3_RECV_HELLO);
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLRecvHelloRetryRequest

    \Description
        Process Hello Retry Request from server (TLS1.3+) as per
        https://tools.ietf.org/html/rfc8446#section-4.1.4

    \Input *pState      - module state reference
    \Input *pData       - pointer to message data
    \Input iDataSize    - size of packet data

    \Output
        int32_t         - next state (ST_SEND_HELLO, or ST_FAIL_* on error)

    \Notes
        The HelloRetryRequest masquerades as a ServerHello, but with a specific
        ServerRandom value.  This function expects to be given data immediately
        following the ServerRandom.

    \Version 08/08/2017 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _ProtoSSLRecvHelloRetryRequest(ProtoSSLRefT *pState, const uint8_t *pData, int32_t iDataSize)
{
    SecureStateT *pSecure = pState->pSecure;
    int32_t iParseResult;
    const uint8_t *pDataEnd = pData + iDataSize;
    const CipherSuiteT *pCipher;
    uint8_t aSessionId[SSL_SESSID_SIZE];
    uint32_t uSessionLen;

    NetPrintfVerbose((DEBUG_MSG_LIST, 0, "protossl: SSL Msg: Process HelloRetryRequest\n"));

    // validate legacy_version, which must be TLS1.2
    if (pSecure->uSslVersion != PROTOSSL_VERSION_TLS1_2)
    {
        _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_ILLEGAL_PARAMETER);
        return(ST_FAIL_SETUP);
    }

    // as per https://tools.ietf.org/html/rfc8446#section-4.1.4, a second HRR results in a fatal error
    if (pSecure->bHelloRetry)
    {
        NetPrintf(("protossl: received HRR after already receiving an HRR\n"));
        _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_UNEXPECTED_MESSAGE);
        return(ST_FAIL_SETUP);
    }

    // inject synthetic message hash (we only need to do tls1.3 hashes here)
    _ProtoSSLHandshakeHashInject(pState->pSecure, CRYPTHASH_SHA256);
    _ProtoSSLHandshakeHashInject(pState->pSecure, CRYPTHASH_SHA384);

    // read legacy session id
    if ((uSessionLen = _SafeRead8(pData, pDataEnd)) == SSL_SESSID_SIZE)
    {
        _SafeReadBytes(aSessionId, sizeof(aSessionId), pData+1, SSL_SESSID_SIZE, pDataEnd);
    }
    pData += uSessionLen+1;

    /* as per https://tools.ietf.org/html/rfc8446#section-4.1.3, a client receiving a legacy_session_id field that
       does not match what it sent in the ClientHello MUST abort the handshake with an "illegal_parameter" alert */
    if (pSecure->bSentSessionId ? memcmp(pSecure->SessionId, aSessionId, sizeof(pSecure->SessionId)) : uSessionLen != 0)
    {
        NetPrintf(("protossl: received mismatched sessionid\n"));
        _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_ILLEGAL_PARAMETER);
        return(ST_FAIL_SETUP);
    }

    // as per https://tools.ietf.org/html/rfc8446#section-4.1.4, a client receiving a cipher suite that was not offered MUST abort the handshake
    if ((pCipher = _ProtoSSLGetCipher(pSecure, _SafeRead16(pData, pDataEnd))) == NULL)
    {
        NetPrintf(("protossl: received cipher in HelloRetryRequest that we didn't send (ident=%d,%d)\n", pData[0], pData[1]));
        _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_ILLEGAL_PARAMETER);
        return(ST_FAIL_SETUP);
    }
    // save cipher for later verification when we receive the ServerHello
    pSecure->uRetryCipher = pCipher->uIdent;
    pData += 2;

    // skip legacy_compression_method, make sure it's zero
    if (_SafeRead8(pData++, pDataEnd))
    {
        /* as per https://tools.ietf.org/html/rfc8446#section-4.1.2 compression must be disabled; "if a TLS 1.3 ClientHello is
           received with any other value in this field, the server MUST abort the handshake with an "illegal_parameter" alert */
        _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_ILLEGAL_PARAMETER);
        return(ST_FAIL_CONN);
    }

    // parse hello extensions for supported_versions first; this allows extension parsers to know if the connection will be tls1.3 or not
    if ((iParseResult = _ProtoSSLParseHelloExtensions(pState, pData, pDataEnd, SSL_EXTN_SUPPORTED_VERSIONS)) != 0)
    {
        return(iParseResult);
    }
    // parse the rest of the extensions
    if ((iParseResult = _ProtoSSLParseHelloExtensions(pState, pData, pDataEnd, SSL_EXTN_ALL)) != 0)
    {
        return(iParseResult);
    }
    // remember we're in the HelloRetryRequest flow
    pSecure->bHelloRetry = TRUE;
    /* if we hit HRR, we assume that we need to make a change to our curve. let's throw
       away the context and generated key */
    pSecure->bEccContextInitialized = FALSE;
    pSecure->bEccKeyGenerated = FALSE;

    // retry ClientHello
    return(ST3_SEND_HELLO);
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLSendServerHello

    \Description
        Send ServerHello handshake packet

    \Input *pState      - module state reference

    \Output
        int32_t         - next state (ST3_SEND_HELLO, ST3_SEND_EXTN, ST3_SEND_CHANGE,
                                      ST3_SEND_CERT, or ST_FAIL_* on error)

    \Version 10/15/2013 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _ProtoSSLSendServerHello(ProtoSSLRefT *pState)
{
    uint8_t strHead[4];
    uint8_t strBody[512];
    uint8_t *pData = strBody;
    SecureStateT *pSecure = pState->pSecure;
    uint32_t uSslVersion;
    int32_t iNextState;

    // if tls1.3, generate public key (tls1.2 and prior do this in SendServerKeyExchange)
    if (pSecure->uSslVersion >= PROTOSSL_VERSION_TLS1_3)
    {
        const CryptCurveDhT *pEcc;
        uint32_t uCryptUsecs;

        // initialize elliptic curve context if not already initialized
        if ((pEcc = _ProtoSSLEccInitContext(pSecure, pSecure->pEllipticCurve)) == NULL)
        {
            /* if we cannot find the dh functions, there is some configuration mishap or the server is faulty.
               let's fail early here so we can debug the issue instead of a null pointer exception */
            return(ST_FAIL_SETUP);
        }
        // generate the public key; public key is extracted and sent to peer in the key_share extension
        if (pEcc->Public(pSecure->EccContext, NULL, &uCryptUsecs) > 0)
        {
            return(ST3_SEND_HELLO);
        }
        pSecure->bEccKeyGenerated = TRUE;
        NetPrintfVerbose((DEBUG_ENC_PERF, 0, "protossl: SSL Perf (generate public key for server key message) %dms\n",
            uCryptUsecs/1000));
        pSecure->uTimer += uCryptUsecs/1000;
    }

    NetPrintfVerbose((DEBUG_MSG_LIST, 0, "protossl: SSL Msg: Send ServerHello\n"));

    // tls1.3 fixes hello version number at 1.2
    uSslVersion = (pSecure->uSslVersion < PROTOSSL_VERSION_TLS1_3) ? pSecure->uSslVersion : SSL3_TLS1_2;

    // set negotiated protocol version
    *pData++ = (uint8_t)(uSslVersion>>8);
    *pData++ = (uint8_t)(uSslVersion>>0);

    // generate and set server random data
    CryptRandGet(pSecure->ServerRandom, sizeof(pSecure->ServerRandom));
    // set random downgrade bytes as appropriate, as per https://tools.ietf.org/html/rfc8446#section-4.1.3
    if ((pSecure->uSslVersion == SSL3_TLS1_2) && (pState->uSslVersion >= SSL3_TLS1_3))
    {
        ds_memcpy(&pSecure->ServerRandom[24], _SSL3_ServerRandomDowngrade12, sizeof(_SSL3_ServerRandomDowngrade12));
    }
    else if ((pSecure->uSslVersion < SSL3_TLS1_2) && (pState->uSslVersion >= SSL3_TLS1_2))
    {
        ds_memcpy(&pSecure->ServerRandom[24], _SSL3_ServerRandomDowngrade11, sizeof(_SSL3_ServerRandomDowngrade11));
    }
    ds_memcpy(pData, pSecure->ServerRandom, sizeof(pSecure->ServerRandom));
    #if DEBUG_RAW_DATA
    NetPrintMem(pSecure->ServerRandom, sizeof(pSecure->ServerRandom), "ServerRandom");
    #endif
    pData += 32;

    // add sessionid to ServerHello (TLS1.2 and prior) / echo client's legacy sessionid (TLS1.3)
    if (((pSecure->uSslVersion < SSL3_TLS1_3) && pState->bSessionResumeEnabled) || (pSecure->bSessionResume))
    {
        #if DEBUG_RAW_DATA
        NetPrintMem(pSecure->SessionId, sizeof(pSecure->SessionId), "SessionId");
        #endif
        *pData++ = sizeof(pSecure->SessionId);
        ds_memcpy(pData, pSecure->SessionId, sizeof(pSecure->SessionId));
        pData += sizeof(pSecure->SessionId);
    }
    else
    {
        *pData++ = 0;
    }

    // add the selected cipher suite
    *pData++ = (uint8_t)(pSecure->pCipher->uIdent>>8);
    *pData++ = (uint8_t)(pSecure->pCipher->uIdent>>0);

    // add null compression_method
    *pData++ = 0;

    // add extensions; note that for TLS1.3 we only add extensions required to establish secure comms; the rest are sent in EncryptedExtensions
    pData = _ProtoSSLAddHelloExtensions(pState, pData, (uint32_t)sizeof(strBody)-(uint32_t)(pData-strBody), strBody, (pSecure->uSslVersion < PROTOSSL_VERSION_TLS1_3) ? pState->uHelloExtn : 0);

    // setup the header
    strHead[0] = SSL3_MSG_SERVER_HELLO;
    strHead[1] = 0;
    strHead[2] = (uint8_t)((pData-strBody)>>8);
    strHead[3] = (uint8_t)((pData-strBody)>>0);

    // send the packet
    _ProtoSSLSendPacket(pState, SSL3_REC_HANDSHAKE, strHead, 4, strBody, (int32_t)(pData-strBody));

    // determine next state
    if (pSecure->uSslVersion >= PROTOSSL_VERSION_TLS1_3)
    {
        // send encrypted extensions
        iNextState = ST3_SEND_EXTN;
    }
    else
    {
        iNextState = pSecure->bSessionResume ? ST3_SEND_CHANGE : ST3_SEND_CERT;
    }
    return(iNextState);
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLRecvServerHello

    \Description
        Process ServerHello handshake packet

    \Input *pState      - module state reference
    \Input *pData       - packet data
    \Input iDataSize    - size of packet data

    \Output
        int32_t         - next state (ST3_RECV_HELLO, ST3_RECV_CHANGE, ST3_PROC_ASYNC,
                                      or ST_FAIL_* on error)

    \Notes
        Also handles initial parsing of tls1.3 HelloRetryRequest; if an HRR is
        identified, processing is handed off to the HRR-specific handler.

    \Version 03/15/2013 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _ProtoSSLRecvServerHello(ProtoSSLRefT *pState, const uint8_t *pData, int32_t iDataSize)
{
    int32_t iParseResult, iState = ST3_RECV_HELLO;
    const uint8_t *pDataEnd, *pSessionId=NULL, *pCipher;
    SecureStateT *pSecure = pState->pSecure;
    const uint8_t *pDataStart = pData;
    uint8_t aSessionId[SSL_SESSID_SIZE], uSessionLen;

    /* get the location of server hello end; some servers will not send any extension length
       so we need to make sure we don't parse past the end of the packet server hello */
    pDataEnd = pData + iDataSize;

    // get server-specified version of the protocol
    pSecure->uSslVersion = _SafeRead16(pData, pDataEnd);
    pData += 2;

    // save server random data
    _SafeReadBytes(pSecure->ServerRandom, sizeof(pSecure->ServerRandom), pData, sizeof(pSecure->ServerRandom), pDataEnd);
    #if DEBUG_RAW_DATA
    NetPrintMem(pSecure->ServerRandom, sizeof(pSecure->ServerRandom), "ServerRandom");
    #endif
    pData += 32;

    /* as per https://tools.ietf.org/html/rfc8446#section-4.1.3, check for special random value,
       indicating this is a HelloRetryRequest and not a ServerHello */
    if (!memcmp(pSecure->ServerRandom, _SSL3_HelloRetryRequestRandom, sizeof(_SSL3_HelloRetryRequestRandom)))
    {
        return(_ProtoSSLRecvHelloRetryRequest(pState, pData, iDataSize - (pData-pDataStart)));
    }
    
    NetPrintfVerbose((DEBUG_MSG_LIST, 0, "protossl: SSL Msg: Process Server Hello\n"));

    // read sessionid length
    uSessionLen = _SafeRead8(pData++, pDataEnd);
    // save pointer to and skip sessionid
    pSessionId = pData;
    pData += uSessionLen;

    // save cipher offset
    pCipher = pData;
    pData += 2;

    // validate and skip compression
    if (_SafeRead8(pData++, pDataEnd) != 0)
    {
        /* as per https://tools.ietf.org/html/rfc8446#section-4.1.2 compression must be disabled; "if a TLS 1.3 ClientHello is received
           with any other value in this field, the server MUST abort the handshake with an "illegal_parameter" alert.  unlike the server
           flow, the client flow enforces this restriction unconditionally, as our client does not support compression */
        NetPrintf(("protossl: compression_methods not zero\n"));
        _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_ILLEGAL_PARAMETER);
        return(ST_FAIL_CONN);
    }

    // parse hello extensions for supported_versions first; this allows extension parsers to know if the connection will be tls1.3 or not
    if ((iParseResult = _ProtoSSLParseHelloExtensions(pState, pData, pDataEnd, SSL_EXTN_SUPPORTED_VERSIONS)) != 0)
    {
        return(iParseResult);
    }

    // make sure we support version; this must be done after possible supported_versions extension parsing
    if (pSecure->uSslVersion != pState->uSslVersion)
    {
        if ((pSecure->uSslVersion < pState->uSslVersionMin) || (pSecure->uSslVersion > pState->uSslVersion))
        {
            NetPrintf(("protossl: server specified SSL version 0x%04x is not supported\n", pSecure->uSslVersion));
            /* As per http://tools.ietf.org/html/rfc5246#appendix-E.1 - If the version chosen by
                the server is not supported by the client (or not acceptable), the client MUST send a
                "protocol_version" alert message and close the connection. */
            _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_PROTOCOL_VERSION);
            return((pSecure->uSslVersion < pState->uSslVersionMin) ? ST_FAIL_CONN_MINVERS : ST_FAIL_CONN_MAXVERS);
        }
        else
        {
            NetPrintfVerbose((pState->iVerbose, 1, "protossl: downgrading SSL version\n"));
        }
    }
    NetPrintfVerbose((pState->iVerbose, 0, "protossl: using %s to connect to %s\n", _SSL3_strVersionNames[pSecure->uSslVersion&0xff], pState->strHost));

    // parse all other extensions
    if ((iParseResult = _ProtoSSLParseHelloExtensions(pState, pData, pDataEnd, SSL_EXTN_ALL)) != 0)
    {
        return(iParseResult);
    }

    // protect against possible downgrade attack as per https://tools.ietf.org/html/rfc8446#section-4.1.3 
    if ((pSecure->uSslVersion == SSL3_TLS1_2) && (pState->uSslVersion >= SSL3_TLS1_3) && !memcmp(&pSecure->ServerRandom[24], _SSL3_ServerRandomDowngrade12, sizeof(_SSL3_ServerRandomDowngrade12)))
    {
        NetPrintf(("protossl: invalid tls1.3 downgrade detected\n"));
        _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_ILLEGAL_PARAMETER);
        return(ST_FAIL_SETUP);
    }
    else if ((pSecure->uSslVersion < SSL3_TLS1_2) && (pState->uSslVersion >= SSL3_TLS1_2) && !memcmp(&pSecure->ServerRandom[24], _SSL3_ServerRandomDowngrade11, sizeof(_SSL3_ServerRandomDowngrade11)))
    {
        NetPrintf(("protossl: invalid tls1.2 downgrade detected\n"));
        _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_ILLEGAL_PARAMETER);
        return(ST_FAIL_SETUP);
    }

    // get and validate cipher
    if ((pSecure->pCipher = _ProtoSSLGetCipher(pSecure, _SafeRead16(pCipher, pDataEnd))) == NULL)
    {
        NetPrintf(("protossl: no matching cipher (ident=%d,%d)\n", pCipher[0], pCipher[1]));
        /* as per https://tools.ietf.org/html/rfc8446#section-4.1.3: a client receiving a cipher suite that was not offered
           MUST abort the handshake with an "illegal_parameter" alert */
        _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_ILLEGAL_PARAMETER);
        return(ST_FAIL_CONN_NOCIPHER);
    }
    else
    {
        NetPrintfVerbose((pState->iVerbose, 0, "protossl: using cipher suite %s (ident=%d,%d)\n", pSecure->pCipher->strName, pCipher[0], pCipher[1]));
    }

    // if in retry flow, make sure we got the expected cipher as per https://tools.ietf.org/html/rfc8446#section-4.1.4
    if (pSecure->bHelloRetry && (pSecure->pCipher->uIdent != pSecure->uRetryCipher))
    {
        NetPrintf(("protossl: cipher specified in ServerHello does not match the cipher from HelloRetryRequest\n"));
        _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_ILLEGAL_PARAMETER);
        return(ST_FAIL_SETUP);
    }

    // read session info
    if (uSessionLen == SSL_SESSID_SIZE)
    {
        _SafeReadBytes(aSessionId, sizeof(aSessionId), pSessionId, SSL_SESSID_SIZE, pDataEnd);
        #if DEBUG_RAW_DATA
        NetPrintMem(aSessionId, sizeof(aSessionId), "ServerHello session id");
        #endif
    }

    // process session id (tls1.2 and previous only)
    if (pSecure->uSslVersion < PROTOSSL_VERSION_TLS1_3)
    {
        // save server session id
        if (uSessionLen == sizeof(pSecure->SessionId))
        {
            // check for ClientHello/ServerHello session match; a match indicates we are resuming the session and bypassing key exchange
            if (!memcmp(pSecure->SessionId, aSessionId, sizeof(pSecure->SessionId)))
            {
                // set resume and update state
                NetPrintfVerbose((pState->iVerbose, 0, "protossl: resuming previous session\n"));
                pSecure->bSessionResume = TRUE;
                iState = ST3_RECV_CHANGE;
            }
            else
            {
                // save session id for possible future resumption
                ds_memcpy(pSecure->SessionId, aSessionId, sizeof(pSecure->SessionId));
            }
        }

        // clear premaster secret if not resuming
        if (!pSecure->bSessionResume)
        {
            ds_memclr(pSecure->MasterKey, sizeof(pSecure->MasterKey));
        }
    }
    else if (pSecure->bSentSessionId ? memcmp(pSecure->SessionId, aSessionId, sizeof(pSecure->SessionId)) : uSessionLen != 0)
    {
        /* as per https://tools.ietf.org/html/rfc8446#section-4.1.3, a client receiving a legacy_session_id field that
           does not match what it sent in the ClientHello MUST abort the handshake with an "illegal_parameter" alert */
        NetPrintf(("protossl: received mismatched sessionid\n"));
        _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_ILLEGAL_PARAMETER);
        return(ST_FAIL_SETUP);
    }

    // tls1.3+ data is immediately encrypted following ServerHello and keyshare/psk
    if (pSecure->uSslVersion >= PROTOSSL_VERSION_TLS1_3)
    {
        // make sure the server gave us a keyshare we support
        if (pSecure->pEllipticCurve == NULL)
        {
            NetPrintf(("protossl: no supported elliptic curve given in tls1.3 server hello\n"));
            _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_ILLEGAL_PARAMETER);
            return(ST_FAIL_SETUP);
        }
        // update the handshake hash with ServerHello, which hasn't been processed yet
        //$$todo - is this actually required?
        _ProtoSSLRecvHandshakeFinish(pState);
        // set up async execution to build secrets and key material
        iState = _ProtoSSLUpdateSetAsyncState(pState, _ProtoSSLBuildHandshakeKey, iState, ST_FAIL_SETUP, SSL3_ALERT_DESC_INTERNAL_ERROR);
    }

    // return new state
    return(iState);
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLSendEncryptedExtensions

    \Description
        Send EncryptedExtensions handshake message (TLS1.3+ only)

    \Input *pState      - module state reference

    \Output
        int32_t         - next state (ST3_SEND_EXTN, ST3_SEND_CERT_REQ or ST3_SEND_CERT)

    \Version 01/26/2017 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _ProtoSSLSendEncryptedExtensions(ProtoSSLRefT *pState)
{
    uint8_t strBody[512], strHead[4], *pData = strBody+2;
    uint32_t uExtnLen;

    // data after ServerHello is encrypted
    if (_ProtoSSLBuildHandshakeKey(pState) > 0)
    {
        NetPrintfVerbose((pState->iVerbose, 1, "protossl: generating handshake key\n"));
        return(ST3_SEND_EXTN);
    }

    NetPrintfVerbose((DEBUG_MSG_LIST, 0, "protossl: SSL Msg: Send EncryptedExtensions\n"));

    // add extensions to message body
    pData = _ProtoSSLAddHelloExtensions(pState, pData, (uint32_t)sizeof(strBody)-2, strBody, pState->uHelloExtn);
    uExtnLen = pData - strBody - 2;
    // set extn length
    strBody[0] = (uint8_t)(uExtnLen>>8);
    strBody[1] = (uint8_t)(uExtnLen>>0);

    // setup the header
    strHead[0] = SSL3_MSG_ENCRYPTED_EXTENSIONS;
    strHead[1] = 0;
    strHead[2] = (uint8_t)((uExtnLen+2)>>8);
    strHead[3] = (uint8_t)((uExtnLen+2)>>0);

    // setup packet for send
    _ProtoSSLSendPacket(pState, SSL3_REC_HANDSHAKE, strHead, 4, strBody, uExtnLen+2);

    // return next state
    return((pState->iClientCertLevel > 0) ? ST3_SEND_CERT_REQ : ST3_SEND_CERT);
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLRecvEncryptedExtensions

    \Description
        Process EncryptedExtensions handshake packet (TLS1.3+ only)

    \Input *pState      - module state reference
    \Input *pData       - packet data
    \Input iDataSize    - size of packet data

    \Output
        int32_t         - next state (ST3_RECV_FINISH, ST3_RECV_HELLO, or ST_FAIL_* on error)

    \Version 01/26/2017 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _ProtoSSLRecvEncryptedExtensions(ProtoSSLRefT *pState, const uint8_t *pData, int32_t iDataSize)
{
    SecureStateT *pSecure = pState->pSecure;
    int32_t iParseResult, iNextState = (pSecure->bSessionResume) ? ST3_RECV_FINISH : ST3_RECV_HELLO;

    NetPrintfVerbose((DEBUG_MSG_LIST, 0, "protossl: SSL Msg: Process EncryptedExtensions\n"));

    // parse the extensions
    iParseResult = _ProtoSSLParseHelloExtensions(pState, pData, pData+iDataSize, SSL_EXTN_ALL);

    // move to next state
    return((iParseResult == 0) ? iNextState : iParseResult);
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLUpdateSendCertificate

    \Description
        Send Certificate handshake packet

    \Input *pState      - module state reference

    \Output
        int32_t         - next state (ST3_SEND_KEY, ST3_SEND_VERIFY, ST3_SEND_CERT_REQ,
                                      or ST3_SEND_DONE)

    \Version 10/15/2013 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _ProtoSSLSendCertificate(ProtoSSLRefT *pState)
{
    SecureStateT *pSecure = pState->pSecure;
    uint8_t strHead[4];
    uint8_t strBody[4096];
    int32_t iCertSize, iNumCerts, iBodySize=0, iExtnSize=0;
    int32_t iState = (pSecure->uSslVersion < PROTOSSL_VERSION_TLS1_3) ? ST3_SEND_KEY : ST3_SEND_VERIFY;

    NetPrintfVerbose((DEBUG_MSG_LIST, 0, "protossl: SSL Msg: Send Certificate\n"));

    // set certificate_request_context
    if (pSecure->uSslVersion >= PROTOSSL_VERSION_TLS1_3)
    {
        strBody[iBodySize++] = 0;
        iExtnSize = 2;
    }

    /* if we have a certificate, add it to body $$todo if client we need to validate certificate matches sigalg/cipher, and send an
       empty message if it does not.  if server we should have already validated that or aborted the connection if it does not */
    if (pState->pCertificate != NULL)
    {
        iCertSize = pState->pCertificate->iCertSize;
        ds_memcpy_s(strBody+iBodySize+6, sizeof(strBody)-iBodySize-6, pState->pCertificate->aCertData, pState->pCertificate->iCertSize);
    }
    else if (pState->bServer)
    {
        /* as per https://tools.ietf.org/html/rfc8446#section-4.4.2 a server's certificate list MUST not be empty; this is
           a general practice in earlier versions but not specifically enforced prior */ 
        NetPrintf(("protossl: no valid certificate\n"));
        _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_HANDSHAKE_FAILURE);
        return(ST_FAIL_CERT_INVALID);
    }
    else
    {
        /* as per https://tools.ietf.org/html/rfc5246#section-7.4.6, a client MUST send an empty certificate_list if it does not have an
           appropriate certificate to send in response to the server's authentication request. */
        iCertSize = 0;
        // if we're a tls1.3 client skip sending CertificateVerify and go directly to Finished
        if (pSecure->uSslVersion >= SSL3_TLS1_3)
        {
            iState = ST3_SEND_FINISH;
        }
    }
    iNumCerts = (iCertSize) ? 1 : 0;

    // copy the certificate list into message body
    strBody[iBodySize++] = 0;
    strBody[iBodySize++] = (uint8_t)(((iCertSize+iExtnSize)+(iNumCerts*3))>>8);
    strBody[iBodySize++] = (uint8_t)(((iCertSize+iExtnSize)+(iNumCerts*3))>>0);
    if (iNumCerts)
    {
        strBody[iBodySize++] = 0;
        strBody[iBodySize++] = (uint8_t)((iCertSize)>>8);
        strBody[iBodySize++] = (uint8_t)((iCertSize)>>0);
    }
    iBodySize += iCertSize;

    /* set Certificate extensions - note that this field is expected after every certificate, so this
       code only works when a single certificate is being sent */
    if (pSecure->uSslVersion >= PROTOSSL_VERSION_TLS1_3)
    {
        strBody[iBodySize++] = 0;
        strBody[iBodySize++] = 0;
    }

    // setup the header
    strHead[0] = SSL3_MSG_CERTIFICATE;
    strHead[1] = 0;
    strHead[2] = (uint8_t)((iBodySize)>>8);
    strHead[3] = (uint8_t)((iBodySize)>>0);

    // setup packet for send
    _ProtoSSLSendPacket(pState, SSL3_REC_HANDSHAKE, strHead, 4, strBody, iBodySize);

    /* remember we sent a certificate (since we will send an empty message if no certificate
       is available, only remember if we *actually* sent a certificate).  this is used later
       to determine if we should send a CertificateVerify handshake message */
    pSecure->bSentCert = iNumCerts ? TRUE : FALSE;

    // if we're a server executing a TLS1.2 or prior handshake, override state transition set above
    if ((pSecure->uSslVersion <= SSL3_TLS1_2) && (pState->bServer) && (pSecure->pCipher->uKey == SSL3_KEY_RSA))
    {
        iState = (pState->iClientCertLevel > 0) ? ST3_SEND_CERT_REQ : ST3_SEND_DONE;
    }
    return(iState);
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLRecvCertificate

    \Description
        Process Certificate handshake packet; ref http://tools.ietf.org/html/rfc5246#section-7.4.2

    \Input *pState      - module state reference
    \Input *pData       - packet data
    \Input iDataSize    - size of packet data

    \Output
        int32_t         - next state (ST3_RECV_HELLO, ST_WAIT_CA, or ST_FAIL_* on error)

    \Version 08/26/2011 (szhu) Added multi-certificate support
*/
/********************************************************************************F*/
static int32_t _ProtoSSLRecvCertificate(ProtoSSLRefT *pState, const uint8_t *pData, int32_t iDataSize)
{
    SecureStateT *pSecure = pState->pSecure;
    int32_t iCertSize, iCertHeight, iSize1, iSize2, iResult, iVerify;
    uint8_t aFingerprintId[SSL_FINGERPRINT_SIZE], bCertCached = FALSE;
    X509CertificateT leafCert, prevCert;
    #if DIRTYCODE_LOGGING
    char strIdentSubject[512], strIdentIssuer[512];
    #endif
    const uint8_t *pDataEnd = pData+iDataSize;

    NetPrintfVerbose((DEBUG_MSG_LIST, 0, "protossl: SSL Msg: Process Certificate\n"));

    ds_memclr(&leafCert, sizeof(leafCert));
    ds_memclr(&prevCert, sizeof(prevCert));

    // skip certificate_request_context
    if (pSecure->uSslVersion >= PROTOSSL_VERSION_TLS1_3)
    {
        uint32_t uContextLen = _SafeRead8(pData, pDataEnd);
        NetPrintfVerbose((DEBUG_RAW_DATA, 0, "protossl: certificate_request_context length=%d\n", uContextLen));
        pData += uContextLen + 1;
        iDataSize -= uContextLen + 1;
    }

    // get outer size
    if ((iCertSize = iSize1 = _SafeRead24(pData, pDataEnd)) <= 3)
    {
        NetPrintf(("protossl: no certificate included in certificate message\n"));
        if (!pState->bServer)
        {
            /* as per https://tools.ietf.org/html/rfc8446#section-4.4.2.4, If the server supplies an empty Certificate message, the client MUST
               abort the handshake with a "decode_error" alert. */
            _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_DECODE_ERROR);
            iResult = ST_FAIL_CERT_NONE;
        }
        else if (pState->bServer && (pState->iClientCertLevel == 2))
        {
            /* as per http://tools.ietf.org/html/rfc5246#section-7.4.6 (TLS 1.2), a client providing an empty certificate response to a certificate
               request can either be ignored and treated as unauthenticated, or responded to with a fatal handshake failure.  a server
               MUST always provide a certificate if the agreed-upon key exchange method uses certificates for authentication
               as per https://tools.ietf.org/html/rfc8446#section-4.4.2.4 (TLS 1.3), If the client does not send any certificates (i.e., it sends an empty Certificate message),
               the server MAY at its discretion either continue the handshake without client authentication or abort the handshake with a "certificate_required" alert.
               Also, if some aspect of the certificate chain was unacceptable (e.g., it was not signed by a known, trusted CA), the server MAY at its discretion either
               continue the handshake (considering the client unauthenticated) or abort the handshake. */
            _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, pSecure->uSslVersion < SSL3_TLS1_3 ? SSL3_ALERT_DESC_HANDSHAKE_FAILURE : SSL3_ALERT_DESC_CERTIFICATE_REQUIRED);
            iResult = ST_FAIL_CERT_NONE;
        }
        else
        {
            iResult = (pSecure->uSslVersion < SSL3_TLS1_3) ? ST3_RECV_HELLO : ST3_RECV_FINISH;
        }
        return(iResult);
    }
    else if (iCertSize != (iDataSize-3))
    {
        NetPrintf(("protossl: invalid certificate length\n"));
        _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_BAD_CERTFICIATE);
        return(ST_FAIL_CERT_INVALID);
    }
    else
    {
        // remember we received a certificate in the certificate message
        pSecure->bRecvCert = TRUE;

        // generate a fingerprint of entire certificate envelope
        _ProtoSSLGenerateFingerprint(aFingerprintId, sizeof(aFingerprintId), pData, iCertSize, CRYPTHASH_SHA1);

        // see if we've already validated this chain
        if (_CertValidHistoryCheck(aFingerprintId, iCertSize))
        {
            NetPrintfVerbose((DEBUG_VAL_CERT, 0, "protossl: matched certificate fingerprint in certificate validity history\n"));
            bCertCached = TRUE;
        }
    }

    // skip outer size
    pData += 3;

    // process certificates
    for (iVerify = -1, iCertHeight = 0; (iSize1 > 0) && (iVerify != 0); pData += iSize2, iSize1 -= iSize2)
    {
        uint64_t uTime;
        int32_t iParse;

        // get certificate size
        iSize2 = _SafeRead24(pData, pDataEnd);

        // make sure certificate size isn't too large
        if (iSize2 > iSize1)
        {
            NetPrintf(("protossl: _ServerCert: certificate is larger than envelope (%d/%d)\n", iSize1, iSize2));
            _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_BAD_CERTFICIATE);
            return(ST_FAIL_CERT_INVALID);
        }

        // skip certificate object length
        pData += 3;
        iSize1 -= 3;

        // parse the certificate
        if ((iParse = _AsnParseCertificate(&pSecure->Cert, pData, iSize2)) < 0)
        {
            NetPrintf(("protossl: _ServerCert: x509 cert invalid (error=%d)\n", iParse));
            _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_BAD_CERTFICIATE);
            return(ST_FAIL_CERT_INVALID);
        }
        #if DEBUG_VAL_CERT
        _CertificateDebugPrint(&pSecure->Cert, "parsed certificate");
        #endif  

        // skip TLS1.3 certificate extension field
        if (pSecure->uSslVersion >= PROTOSSL_VERSION_TLS1_3)
        {
            const uint8_t *pExtn = pData + iSize2;
            uint16_t uExtnLen = _SafeRead16(pExtn, pDataEnd);
            iSize2 += uExtnLen + 2;
        }

        // allow verification bypass; note this must come after first certificate is parsed since we need it later for key exchange
        if (pState->bAllowAnyCert)
        {
            NetPrintfVerbose((pState->iVerbose, 0, "protossl: bypassing certificate verify (disabled)\n"));
            return(ST3_RECV_HELLO);
        }

        // validate date range
        if ((uTime = ds_timeinsecs()) != 0)
        {
            if ((uTime < pSecure->Cert.uGoodFrom) || (uTime > pSecure->Cert.uGoodTill))
            {
                #if DIRTYCODE_LOGGING
                struct tm TmTime;
                char strTime[32];
                _CertificateDebugPrint(&pSecure->Cert, "certificate failed date range validation:");
                NetPrintf(("protossl:    from: %s\n", ds_timetostr(ds_secstotime(&TmTime, pSecure->Cert.uGoodFrom), TIMETOSTRING_CONVERSION_ISO_8601, FALSE, strTime, sizeof(strTime))));
                NetPrintf(("protossl:    till: %s\n", ds_timetostr(ds_secstotime(&TmTime, pSecure->Cert.uGoodTill), TIMETOSTRING_CONVERSION_ISO_8601, FALSE, strTime, sizeof(strTime))));
                #endif
                iVerify = SSL_ERR_CERT_INVALIDDATE;
                pSecure->bDateVerifyFailed = TRUE;
            }
        }

        // processing specific to first (leaf) certificate
        if (iCertHeight == 0)
        {
            // ensure certificate was issued to this host (server certificates only)
            if (!pState->bServer && (_CertificateMatchHostname(pState->strHost, pSecure->Cert.Subject.strCommon) != 0))
            {
                // check against alternative name(s), if present
                if (_CertificateMatchSubjectAlternativeName(pState->strHost, pSecure->Cert.pSubjectAlt, pSecure->Cert.iSubjectAltLen) != 0)
                {
                    NetPrintf(("protossl: _ServerCert: certificate not issued to this host (%s != %s)\n", pState->strHost, pSecure->Cert.Subject.strCommon));
                    _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_CERTIFICATE_UNKNOWN);
                    _CertificateDebugPrint(&pSecure->Cert, "cert");
                    _CertificateSetFailureInfo(pState, &pSecure->Cert, FALSE);
                    return(ST_FAIL_CERT_HOST);
                }
            }
            // save leaf cert as we will need it later for handshake
            ds_memcpy(&leafCert, &pSecure->Cert, sizeof(leafCert));
            // debug display of leaf certificate signature algorithm and signature length
            NetPrintfVerbose((pState->iVerbose, 0, "protossl: sigalg=%s, siglen=%d\n", _SSL3_strSignatureTypes[pSecure->Cert.iSigType-ASN_OBJ_RSA_PKCS_MD5], pSecure->Cert.iSigSize*8));

            // if we cached this cert chain allow bypass; this must come after we've verified the hostname matches
            if (bCertCached)
            {
                NetPrintfVerbose((pState->iVerbose, 0, "protossl: bypassing certificate verify (cached)\n"));
                iVerify = 0;
                break;
            }
        }
        else
        {
            // check to see if the cert is the next cert in the trust chain anchored by the leaf certificate
            if ((_CertificateCompareIdent(&pSecure->Cert.Subject, &prevCert.Issuer, prevCert.iCertIsCA) != 0) ||
                ((pSecure->Cert.iKeyModSize != prevCert.iSigSize) && (pSecure->Cert.iKeyType != ASN_OBJ_ECDSA_KEY)))
            {
                // not part of our chain, so skip it
                _CertificateDebugPrint(&pSecure->Cert, "_ServerCert: certificate not part of chain, skipping:");
                continue;
            }

            // check basic constraints (CA)
            if (pSecure->Cert.iCertIsCA == FALSE)
            {
                continue;
            }
                
            // check basic constraints (pathLenConstraints)
            if ((pSecure->Cert.iMaxHeight != 0) && (iCertHeight > pSecure->Cert.iMaxHeight))
            {
                continue;
            }

            // verify previous cert's signature with current cert's public key
            if ((_ProtoSSLVerifyCertificateSignature(pSecure, &prevCert, pSecure->Cert.KeyModData, pSecure->Cert.iKeyModSize,
                pSecure->Cert.KeyExpData, pSecure->Cert.iKeyExpSize, pSecure->Cert.iKeyType, pSecure->Cert.iCrvType)) != 0)
            {
                continue;
            }

            NetPrintfVerbose((pState->iVerbose, 0, "protossl: cert (%s) verified by ca (%s)\n", _CertificateDebugFormatIdent(&prevCert.Subject, strIdentSubject, sizeof(strIdentSubject)),
                _CertificateDebugFormatIdent(&prevCert.Issuer, strIdentIssuer, sizeof(strIdentIssuer))));
        }

        // track cert height
        iCertHeight += 1;
        // force _VerifyCertificate to update pState->CertInfo on cert failure
        pState->bCertInfoSet = FALSE;
        // see if we can verify against a trust anchor; a success here means we are done parsing the certificate chain, whether there are more certificates or not
        iVerify = _ProtoSSLVerifyCertificate(pState, pSecure, &pSecure->Cert, FALSE);

        // save current cert for validation of next cert
        ds_memcpy(&prevCert, &pSecure->Cert, sizeof(prevCert));
    }

    // process verification result
    if (iVerify != 0)
    {
        #if DIRTYCODE_LOGGING
        if (iVerify == SSL_ERR_GOSCA_INVALIDUSE)
        {
            NetPrintf(("protossl: gos ca (%s) may not be used for non-ea domain %s\n", _CertificateDebugFormatIdent(&pSecure->Cert.Issuer, strIdentSubject, sizeof(strIdentSubject)),
                pSecure->Cert.Subject.strCommon));
        }
        else if (iVerify == SSL_ERR_CAPROVIDER_INVALID)
        {
            NetPrintf(("protossl: ca (%s) may not be used as a ca provider\n", _CertificateDebugFormatIdent(&pSecure->Cert.Issuer, strIdentSubject, sizeof(strIdentSubject))));
        }
        else
        {
            _CertificateDebugPrint(&pSecure->Cert, "_VerifyCertificate() failed -- no CA available for this certificate");
            NetPrintf(("protossl: _ServerCert: x509 cert untrusted (error=%d)\n", iVerify));
        }
        #endif

        // check if we need to fetch CA
        if ((iVerify != SSL_ERR_GOSCA_INVALIDUSE) && (_ProtoSSLInitiateCARequest(pState) == 0))
        {
            iResult = ST_WAIT_CA;
        }
        else
        {
            _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_UNKNOWN_CA);
            iResult = ST_FAIL_CERT_NOTRUST;
        }
    }
    else if (pSecure->bDateVerifyFailed)
    {
        // an invalid date is not flagged as a verification failure, so we check the flag directly here
        NetPrintf(("protossl: _ServerCert: failed date validation\n"));
        _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_CERTIFICATE_EXPIRED);
        iResult = ST_FAIL_CERT_BADDATE;
    }
    else
    {
        // cert chain is valid
        NetPrintfVerbose((pState->iVerbose, 0, "protossl: cert (%s) verified by ca (%s)\n", _CertificateDebugFormatIdent(&pSecure->Cert.Subject, strIdentSubject, sizeof(strIdentSubject)),
            _CertificateDebugFormatIdent(&pSecure->Cert.Issuer, strIdentIssuer, sizeof(strIdentIssuer))));

        // process next handshake packet
        iResult = ST3_RECV_HELLO;
        // add cert fingerprint to cert validation history if it's not already there
        if (!bCertCached)
        {
            _CertValidHistoryAdd(aFingerprintId, iCertSize);
        }
    }
    // restore the leaf cert for use during handshake
    if ((iCertHeight != 0) && (iResult != ST_FAIL_CERT_NOTRUST))
    {
        ds_memcpy(&pSecure->Cert, &leafCert, sizeof(pSecure->Cert));
    }

    // still in hello state (if validated)
    return(iResult);
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLSendServerKeyExchange

    \Description
        Send the ServerKeyExchange message (only for ECDHE key exchange)

    \Input *pState      - module state reference

    \Output
        int32_t         - next state (ST3_SEND_KEY, ST3_SEND_CERT_REQ, ST3_SEND_DONE
                                      or ST_FAIL_SETUP on error)

    \Version 03/03/2017 (eesponda)
*/
/********************************************************************************F*/
static int32_t _ProtoSSLSendServerKeyExchange(ProtoSSLRefT *pState)
{
    SecureStateT *pSecure = pState->pSecure;
    uint8_t aBody[1024], aHead[4];
    uint8_t *pData = aBody;

    NetPrintfVerbose((DEBUG_MSG_LIST, 0, "protossl: SSL Msg: Send ServerKeyExchange\n"));

    // if using RSA for key exchange we do not send this message, treat as error
    if (pSecure->pCipher->uKey == SSL3_KEY_RSA)
    {
        NetPrintf(("protossl: _SendServerKey: wrong key exchange algorithm RSA"));
        return(ST_FAIL_SETUP);
    }
    if (pSecure->pSigScheme == NULL)
    {
        if (pSecure->uSslVersion < SSL3_TLS1_2)
        {
            pSecure->pSigScheme = _ProtoSSLGetSignatureScheme((pSecure->pCipher->uSig == SSL3_SIGALG_RSA) ? SSL3_SIGSCHEME_RSA_PKCS1_SHA1 : SSL3_SIGSCHEME_ECDSA_SHA1);
        }
        else
        {
            /* if we failed to find a matching signature algorithm and the key exchange uses ecdhe we fail
               as we won't be able to match a signature algorithm to sign with */
            NetPrintf(("protossl: _SendServerKey: no valid signature algorithm needed for key exchange\n"));
            _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_HANDSHAKE_FAILURE);
            return(ST_FAIL_SETUP);
        }
    }
    // make sure we found a supported elliptic curve if we are using ECC cipher suite
    if (pSecure->pEllipticCurve == NULL)
    {
        NetPrintf(("protossl: _SendServerKey: no matching elliptic curve when using ecc cipher suite\n"));
        _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_HANDSHAKE_FAILURE);
        /* when using ECC cipher suite make sure that we have a suitable elliptic curve.
           we are allowed to try a different cipher suite but I do not believe it is worth
           the hassle of reparsing.
           note: ECDHE-ECDSA is allowed to use a different curve for the ephemeral ECDH key
           in the ServerKeyExchange message */
        return(ST_FAIL_SETUP);
    }

    /* generate public key
       elliptic curve generation takes multiple frames
       so stay in the same state until the operation is complete */
    if (!pSecure->bEccKeyGenerated)
    {
        const CryptCurveDhT *pEcc;
        uint32_t uCryptUsecs;

        // initialize elliptic curve context if not already initialized
        if ((pEcc = _ProtoSSLEccInitContext(pSecure, pSecure->pEllipticCurve)) == NULL)
        {
            /* if we cannot find the dh functions, there is some configuration mishap or the server is faulty.
               let's fail early here so we can debug the issue instead of a null pointer exception */
            return(ST_FAIL_SETUP);
        }
        // generate the public key
        if (pEcc->Public(pSecure->EccContext, NULL, &uCryptUsecs) > 0)
        {
            return(ST3_SEND_KEY);
        }
        pSecure->uPubKeyLength = pEcc->PointFinal(pSecure->EccContext, NULL, FALSE, pSecure->PubKey, sizeof(pSecure->PubKey));
        pSecure->bEccKeyGenerated = TRUE;
        NetPrintfVerbose((DEBUG_ENC_PERF, 0, "protossl: SSL Perf (generate public key for server key message) %dms\n",
            uCryptUsecs/1000));
        pSecure->uTimer += uCryptUsecs/1000;
    }

    // setup the data that needed to be hashed
    *pData++ = SSL3_CURVETYPE_NAMED_CURVE;
    *pData++ = (uint8_t)(pSecure->pEllipticCurve->uIdent>>8);
    *pData++ = (uint8_t)(pSecure->pEllipticCurve->uIdent>>0);
    *pData++ = pSecure->uPubKeyLength;
    ds_memcpy_s(pData, pSecure->uPubKeyLength, pSecure->PubKey, sizeof(pSecure->PubKey));
    pData += pSecure->uPubKeyLength;

    // setup the encryption for the server key exchange signature
    if (!pSecure->bSigGenerated)
    {
        CryptHashTypeE eHashType = _SSL3_HashIdToCrypt[pSecure->pSigScheme->SigAlg.uHashId];
        const CryptHashT *pHash;

        // calculate signature
        if ((pHash = CryptHashGet(eHashType)) != NULL)
        {
            uint8_t aSigObj[SSL_SIG_MAX], aHashDigest[CRYPTHASH_MAXDIGEST];
            int32_t iPrivateKeySize, iHashSize, iSigSize;
            X509PrivateKeyT PrivateKey;

            // decode private key, extract key modulus and private exponent
            if ((iPrivateKeySize = _CertificateDecodePrivate(pState->pPrivateKey, pState->iPrivateKeyLen, &PrivateKey)) < 0)
            {
                NetPrintf(("protossl: unable to decode private key\n"));
                _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_INTERNAL_ERROR);
                return(ST_FAIL_SETUP);
            }

            // generate signature hash and encode (where appropriate) to create signature hash object
            if ((pSecure->uSslVersion < SSL3_TLS1_2) && (pSecure->pCipher->uSig != SSL3_SIGALG_ECDSA))
            {
                iSigSize = _ProtoSSLGenerateSignatureHash(pSecure, CryptHashGet(CRYPTHASH_MD5), aSigObj, aBody, (uint32_t)(pData-aBody));
                iSigSize += _ProtoSSLGenerateSignatureHash(pSecure, CryptHashGet(CRYPTHASH_SHA1), aSigObj+iSigSize, aBody, (uint32_t)(pData-aBody));
            }
            else
            {
                iHashSize = _ProtoSSLGenerateSignatureHash(pSecure, pHash, aHashDigest, aBody, (uint32_t)(pData-aBody));
                if (pSecure->pSigScheme->uVerifyScheme == SSL3_SIGVERIFY_RSA_PKCS1)
                {
                    iSigSize = _AsnWriteDigitalHashObject(aSigObj, sizeof(aSigObj), aHashDigest, iHashSize, eHashType);
                }
                else if (pSecure->pSigScheme->uVerifyScheme == SSL3_SIGVERIFY_RSA_PSS)
                {
                    iSigSize = _Pkcs1EncodeEMSA_PSS(aSigObj, PrivateKey.Modulus.iObjSize, aHashDigest, iHashSize, eHashType);
                }
                else // ecdsa signatures have no additional encoding
                {
                    ds_memcpy_s(aSigObj, sizeof(aSigObj), aHashDigest, iHashSize);
                    iSigSize = iHashSize;
                }
            }

            // start async signature generation
            return(_ProtoSSLGenerateSignature(pState, pSecure, pSecure->pSigScheme, aSigObj, iSigSize, &PrivateKey, ST3_SEND_KEY));
        }
    }

    // signature is ready, format the message
    if (pSecure->uSslVersion >= SSL3_TLS1_2)
    {
        *pData++ = (uint8_t)(pSecure->pSigScheme->uIdent>>8);
        *pData++ = (uint8_t)(pSecure->pSigScheme->uIdent>>0);
    }
    *pData++ = (uint8_t)(pSecure->SigVerify.iSigSize>>8);
    *pData++ = (uint8_t)(pSecure->SigVerify.iSigSize>>0);
    ds_memcpy(pData, pSecure->SigVerify.aSigData, pSecure->SigVerify.iSigSize);
    pData += pSecure->SigVerify.iSigSize;

    #if DEBUG_RAW_DATA
    NetPrintMem(pSecure->SigVerify.aSigData, pSecure->SigVerify.iSigSize, "encrypted server key exchange signature");
    #endif

    // format the packet header
    aHead[0] = SSL3_MSG_SERVER_KEY;
    aHead[1] = 0;
    aHead[2] = (uint8_t)((pData-aBody)>>8);
    aHead[3] = (uint8_t)((pData-aBody)>>0);

    // setup packet for send
    _ProtoSSLSendPacket(pState, SSL3_REC_HANDSHAKE, aHead, sizeof(aHead), aBody, (int32_t)(pData-aBody));
    return((pState->iClientCertLevel > 0) ? ST3_SEND_CERT_REQ : ST3_SEND_DONE);
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLRecvServerKeyExchange

    \Description
        Process ServerKeyExchange handshake packet; not used with RSA cipher suites

    \Input *pState      - module state reference
    \Input *pData       - packet data
    \Input iDataSize    - size of packet data

    \Output
        int32_t         - next state (ST3_RECV_HELLO, ST_PROC_ASYNC, or ST_FAIL_SETUP on error)

    \Version 03/08/2002 (gschaefer)
*/
/********************************************************************************F*/
static int32_t _ProtoSSLRecvServerKeyExchange(ProtoSSLRefT *pState, const uint8_t *pData, int32_t iDataSize)
{
    int32_t iIndex;
    SecureStateT *pSecure = pState->pSecure;
    CryptHashTypeE eHashType;
    const CryptHashT *pHash;
    const uint8_t *pBegin = pData, *pDataEnd = pData+iDataSize, *pSigData;
    const SignatureSchemeT *pSigScheme;
    int32_t iNextState = ST3_RECV_HELLO;

    NetPrintfVerbose((DEBUG_MSG_LIST, 0, "protossl: SSL Msg: Process ServerKeyExchange\n"));

    // if using RSA for key exchange we do not receive this message, treat as error
    if (pSecure->pCipher->uKey == SSL3_KEY_RSA)
    {
        NetPrintf(("protossl: _ServerKey: wrong key exchange algorithm RSA\n"));
        return(ST_FAIL_SETUP);
    }
    // make sure it is using the curve type we support
    if ((_SafeRead8(pData, pDataEnd) == SSL3_CURVETYPE_NAMED_CURVE) && (pData+3 <= pDataEnd))
    {
        // validate the elliptic curve sent by the server
        for (iIndex = 0; iIndex < (signed)(sizeof(_SSL3_EllipticCurves)/sizeof(*_SSL3_EllipticCurves)); iIndex += 1)
        {
            // skip non-matching elliptic curve
            if ((pData[1] != (uint8_t)(_SSL3_EllipticCurves[iIndex].uIdent >> 8)) || (pData[2] != (uint8_t)(_SSL3_EllipticCurves[iIndex].uIdent)))
            {
                continue;
            }
            // found an elliptic curve
            pSecure->pEllipticCurve = &_SSL3_EllipticCurves[iIndex];
            NetPrintfVerbose((pState->iVerbose, 0, "protossl: _ServerKey: using named curve %s (ident=0x%04x)\n", pSecure->pEllipticCurve->strName, pSecure->pEllipticCurve->uIdent));
            break;
        }
        pData += 3;
    }
    // make sure the server sent us one of our supported elliptic curves
    if (pSecure->pEllipticCurve == NULL)
    {
        NetPrintf(("protossl: _ServerKey: no matching named curve when using ecdhe key exchange\n"));
        return(ST_FAIL_SETUP);
    }

    // copy the public key
    pSecure->uPubKeyLength = _SafeRead8(pData++, pDataEnd);
    _SafeReadBytes(pSecure->PubKey, sizeof(pSecure->PubKey), pData, pSecure->uPubKeyLength, pDataEnd);
    pData += pSecure->uPubKeyLength;

    // if we attempted a TLS 1.3 connection but were downgraded, reset the ecc parameters
    if (pState->uSslVersion >= PROTOSSL_VERSION_TLS1_3)
    {
        pSecure->bEccContextInitialized = FALSE;
        pSecure->bEccKeyGenerated = FALSE;
    }

    // get signature scheme
    if (pSecure->uSslVersion < SSL3_TLS1_2)
    {
        // EC ciphers in TLS versions prior to 1.2 do not include their signature algorithm in the packet data and are assumed to be SHA1
        pSigScheme = _ProtoSSLGetSignatureScheme(pSecure->Cert.iKeyType == ASN_OBJ_ECDSA_KEY ? SSL3_SIGSCHEME_ECDSA_SHA1 : SSL3_SIGSCHEME_RSA_PKCS1_SHA1);
        pSigData = pData;
    }
    else
    {
        if ((pSigScheme = _ProtoSSLGetSignatureScheme(_SafeRead16(pData, pDataEnd))) == NULL)
        {
            NetPrintf(("protossl: _ServerKey: unsupported signature scheme or signature algorithm\n"));
            return(ST_FAIL_SETUP);
        }
        pSigData = pData+2;
    }
    // get signature hash
    eHashType = _SSL3_HashIdToCrypt[pSigScheme->SigAlg.uHashId];

    // calculate and verify the signature
    if ((pHash = CryptHashGet(eHashType)) != NULL)
    {
        uint8_t aHashDigest[CRYPTHASH_MAXDIGEST];
        int32_t iHashSize, iSigSize = _SafeRead16(pSigData, pDataEnd);
        pSigData += 2;

        // create signature hash
        if ((pSecure->uSslVersion < SSL3_TLS1_2) && (pSigScheme->uIdent != SSL3_SIGSCHEME_ECDSA_SHA1))
        {
            // ecdhe-rsa-sha cipher suites
            iHashSize = _ProtoSSLGenerateSignatureHash(pSecure, CryptHashGet(CRYPTHASH_MD5), aHashDigest, pBegin, (uint32_t)(pData-pBegin));
            iHashSize += _ProtoSSLGenerateSignatureHash(pSecure, CryptHashGet(CRYPTHASH_SHA1), aHashDigest+iHashSize, pBegin, (uint32_t)(pData-pBegin));
        }
        else
        {
            iHashSize = _ProtoSSLGenerateSignatureHash(pSecure, pHash, aHashDigest, pBegin, (uint32_t)(pData-pBegin));
        }

        // verify the signature; note this is executed iteratively
        iNextState = _ProtoSSLVerifySignature(pState, pSecure, pSigScheme, pSigData, iSigSize, aHashDigest, iHashSize, pSecure->uSslVersion >= SSL3_TLS1_2 ? eHashType : CRYPTHASH_NULL, ST3_RECV_HELLO);
    }
    else
    {
        NetPrintf(("protossl: _ServerKey: unsupported signature hash\n"));
        return(ST_FAIL_SETUP);
    }

    return(iNextState);
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLSendCertificateRequest

    \Description
        Send CertificateRequest handshake packet

    \Input *pState      - module state reference

    \Output
        int32_t         - next state (ST3_SEND_DONE)

    \Version 10/15/2013 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _ProtoSSLSendCertificateRequest(ProtoSSLRefT *pState)
{
    SecureStateT *pSecure = pState->pSecure;
    uint8_t strHead[4], strBody[128];
    int32_t iBodySize;

    NetPrintfVerbose((DEBUG_MSG_LIST, 0, "protossl: SSL Msg: Send Certificate Request\n"));

    if (pSecure->uSslVersion < SSL3_TLS1_3)
    {
        // setup the body
        iBodySize = 0;
        strBody[iBodySize++] = 2;                   // number of certificate types
        strBody[iBodySize++] = SSL3_SIGN_RSA;       // rsa signing support
        strBody[iBodySize++] = SSL3_SIGN_ECDSA;     // ecdsa signing support

        // add TLS1.2 required SignatureAndHashAlgorithm as per http://tools.ietf.org/html/rfc5246#section-7.4.4
        if (pSecure->uSslVersion >= SSL3_TLS1_2)
        {
            int32_t iScheme, iNumSchemes = sizeof(_SSL3_SignatureSchemes)/sizeof(_SSL3_SignatureSchemes[0]);
            strBody[iBodySize++] = 0;
            strBody[iBodySize++] = iNumSchemes * 2;
            for (iScheme = 0; iScheme < iNumSchemes; iScheme += 1)
            {
                strBody[iBodySize++] = (uint8_t)(_SSL3_SignatureSchemes[iScheme].uIdent>>8);
                strBody[iBodySize++] = (uint8_t)(_SSL3_SignatureSchemes[iScheme].uIdent>>0);
            }
        }

        // number of certificate authorities (not supported)
        strBody[iBodySize++] = 0;
        strBody[iBodySize++] = 0;
    }
    else
    {
        const uint8_t *pDataEnd;
        // write empty certificate_request_context, which we do not require as we don't support post-handshake authentication
        strBody[0] = 0;
        // add signature_algorithms extension
        pDataEnd = _ProtoSSLAddHelloExtensions(pState, strBody+1, sizeof(strBody)-1, strBody, PROTOSSL_HELLOEXTN_SIGALGS);
        iBodySize = pDataEnd - strBody;
    }

    // setup the header
    strHead[0] = SSL3_MSG_CERT_REQ;
    strHead[1] = 0;
    strHead[2] = 0;
    strHead[3] = iBodySize;

    // setup packet for send
    _ProtoSSLSendPacket(pState, SSL3_REC_HANDSHAKE, strHead, sizeof(strHead), strBody, iBodySize);
    return((pSecure->uSslVersion < SSL3_TLS1_3) ? ST3_SEND_DONE : ST3_SEND_CERT);
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLRecvCertificateRequest

    \Description
        Process CertificateRequest handshake packet

    \Input *pState      - module state reference
    \Input *pData       - packet data
    \Input iDataSize    - size of packet data

    \Output
        int32_t         - next state (ST3_RECV_HELLO, or ST_FAIL_* on error)

    \Version 10/18/2013 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _ProtoSSLRecvCertificateRequest(ProtoSSLRefT *pState, const uint8_t *pData, int32_t iDataSize)
{
    SecureStateT *pSecure = pState->pSecure;
    const uint8_t *pDataEnd = pData + iDataSize;
    int32_t iType, iNumTypes;

    NetPrintfVerbose((DEBUG_MSG_LIST, 0, "protossl: SSL Msg: Process CertificateRequest\n"));

    // process tls1.2 and prior certificate request
    if (pSecure->uSslVersion <= SSL3_TLS1_2)
    {
        uint8_t uSign;
        // make sure an rsa/ecdsa certificate is permissable
        for (iType = 0, iNumTypes = _SafeRead8(pData++, pDataEnd); iType < iNumTypes; iType += 1)
        {
            uSign = _SafeRead8(pData+iType, pDataEnd);
            if ((uSign == SSL3_SIGN_RSA) || (uSign == SSL3_SIGN_ECDSA))
            {
                break;
            }
        }
        // makes sure we got a valid signing algorithm
        if (iType == iNumTypes)
        {
            NetPrintf(("protossl: no valid cert signing option found in RecvCertificateRequest\n"));
            _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_DECODE_ERROR);
            return(ST_FAIL_SETUP);
        }
        // skip past cert types
        pData += iNumTypes;

        // TLS1.2 includes SignatureAndHashAlgorithm specification: http://tools.ietf.org/html/rfc5246#section-7.4.4
        if (pSecure->uSslVersion == SSL3_TLS1_2)
        {
            // get and skip list length
            uint32_t uSigSchemeLen = _SafeRead16(pData, pDataEnd);
            pData += 2;
            // pick first supported signature algorithm
            if ((pSecure->pSigScheme = _ProtoSSLChooseSignatureScheme(pSecure, pState->pCertificate, pData, uSigSchemeLen, pDataEnd)) == NULL)
            {
                NetPrintf(("protossl: no valid signature algorithm found RecvCertificateRequest\n"));
                _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_HANDSHAKE_FAILURE);
                return(ST_FAIL_SETUP);
            }
        }
    }
    else
    {
        // parse tls1.3 certificate request as per https://tools.ietf.org/html/rfc8446#section-4.3.2
        int32_t iParseResult;
        // skip certificate_request_context, which we do not require as we don't support post-handshake authentication
        pData += _SafeRead8(pData, pDataEnd)+1;
        // parse signature_algorithms 
        if ((iParseResult = _ProtoSSLParseHelloExtensions(pState, pData, pDataEnd, SSL_EXTN_SIGNATURE_ALGORITHMS)) != 0)
        {
            return(iParseResult);
        }
    }

    /* As per http://tools.ietf.org/html/rfc5246#section-7.4.6, if no suitable certificate is available,
       the client MUST send a certificate message containing no certificates, so we don't bother to check
       whether we have a certificate here */
    pState->iClientCertLevel = 1;
    return(ST3_RECV_HELLO);
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLSendServerHelloDone

    \Description
        Send ServerHelloDone handshake packet; marks end of server hello sequence

    \Input *pState      - module state reference

    \Output
        int32_t         - next state (ST3_RECV_HELLO)

    \Notes
        Applies to TLS1.2 and prior only

    \Version 10/15/2013 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _ProtoSSLSendServerHelloDone(ProtoSSLRefT *pState)
{
    uint8_t strHead[4];

    NetPrintfVerbose((DEBUG_MSG_LIST, 0, "protossl: SSL Msg: Send ServerHelloDone\n"));

    // setup the header
    strHead[0] = SSL3_MSG_SERVER_DONE;
    strHead[1] = 0;
    strHead[2] = 0;
    strHead[3] = 0;

    // setup packet for send
    _ProtoSSLSendPacket(pState, SSL3_REC_HANDSHAKE, strHead, 4, NULL, 0);
    return(ST3_RECV_HELLO);
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLRecvServerHelloDone

    \Description
        Process ServerHelloDone handshake packet; marks end of server hello sequence

    \Input *pState      - module state reference
    \Input *pData       - packet data
    \Input iDataSize    - size of packet data

    \Output
        int32_t         - next state (ST3_SEND_CERT or ST3_SEND_KEY)

    \Notes
        Applies to TLS1.2 and prior only

    \Version 10/15/2013 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _ProtoSSLRecvServerHelloDone(ProtoSSLRefT *pState, const uint8_t *pData, int32_t iDataSize)
{
    NetPrintfVerbose((DEBUG_MSG_LIST, 0, "protossl: SSL Msg: Process ServerHelloDone\n"));
    return((pState->iClientCertLevel != 0) ? ST3_SEND_CERT : ST3_SEND_KEY);
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLUpdateSendClientKeyExchange

    \Description
        Send ClientKeyExchange handshake packet to the server

    \Input *pState  - module state reference

    \Output
        int32_t     - next state (ST3_SEND_VERIFY or ST3_SEND_CHANGE on completion,
                      ST3_SEND_KEY if computation is ongoing, ST_FAIL_SETUP on error)

    \Version 01/23/2017 (eesponda) Updated to support ECDHE
*/
/********************************************************************************F*/
static int32_t _ProtoSSLSendClientKeyExchange(ProtoSSLRefT *pState)
{
    SecureStateT *pSecure = pState->pSecure;
    uint8_t aHead[6];

    // handle rsa key exchange
    if (pState->pSecure->pCipher->uKey == SSL3_KEY_RSA)
    {
        if (!pSecure->bRSAContextInitialized)
        {
            // build pre-master secret
            CryptRandGet(pSecure->PreMasterKey, sizeof(pSecure->PreMasterKey));
            /* From http://tools.ietf.org/html/rfc5246#section-7.4.7.1, client_version: The version requested
               by the client in the ClientHello.  This is used to detect version roll-back attacks */
            pSecure->PreMasterKey[0] = (uint8_t)(pSecure->uSslClientVersion>>8);
            pSecure->PreMasterKey[1] = (uint8_t)(pSecure->uSslClientVersion>>0);

            // init rsa state
            if (CryptRSAInit(&pSecure->RSAContext, pSecure->Cert.KeyModData, pSecure->Cert.iKeyModSize, pSecure->Cert.KeyExpData, pSecure->Cert.iKeyExpSize))
            {
                _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_INTERNAL_ERROR);
                return(ST_FAIL_SETUP);
            }
            CryptRSAInitMaster(&pSecure->RSAContext, pSecure->PreMasterKey, sizeof(pSecure->PreMasterKey));
            pSecure->bRSAContextInitialized = TRUE;
        }

        // encrypt the premaster key (iterative)
        if (CryptRSAEncrypt(&pSecure->RSAContext, 1))
        {
            return(ST3_SEND_KEY);
        }
        pSecure->bRSAContextInitialized = FALSE;

        // update ssl perf timer
        NetPrintfVerbose((DEBUG_ENC_PERF, 0, "protossl: SSL Perf (encrypt rsa client premaster secret) %dms (%d calls)\n",
            pSecure->RSAContext.uCryptMsecs, pSecure->RSAContext.uNumExpCalls));
        pSecure->uTimer += pSecure->RSAContext.uCryptMsecs;

        // build master secret
        _ProtoSSLBuildKey(pState, pSecure->MasterKey, sizeof(pSecure->MasterKey), pSecure->PreMasterKey, sizeof(pSecure->PreMasterKey),
            pSecure->ClientRandom, pSecure->ServerRandom, sizeof(pSecure->ClientRandom), "master secret",
            pSecure->uSslVersion);

        // setup the header
        aHead[0] = SSL3_MSG_CLIENT_KEY;
        aHead[1] = 0;
        aHead[2] = (uint8_t)((pSecure->Cert.iKeyModSize+2)>>8);
        aHead[3] = (uint8_t)((pSecure->Cert.iKeyModSize+2)>>0);
        aHead[4] = (uint8_t)(pSecure->Cert.iKeyModSize>>8);
        aHead[5] = (uint8_t)(pSecure->Cert.iKeyModSize>>0);

        // setup packet for send
        _ProtoSSLSendPacket(pState, SSL3_REC_HANDSHAKE, aHead, 6, pSecure->RSAContext.EncryptBlock, pSecure->RSAContext.iKeyModSize);
    }
    else
    {
        uint8_t aPublicKey[128];
        int32_t iPublicKeySize;
        uint32_t uCryptUsecs;
        const CryptCurveDhT *pEcc;

        /* generate public key; elliptic curve generation takes multiple frames
           so stay in the same state until the operation is complete */

        // initialize elliptic curve context if not already initialized
        if ((pEcc = _ProtoSSLEccInitContext(pSecure, pSecure->pEllipticCurve)) == NULL)
        {
            /* if we cannot find the dh functions, there is some configuration mishap or the server is faulty.
               let's fail early here so we can debug the issue instead of a null pointer exception */
            return(ST_FAIL_SETUP);
        }
        // generate the public key
        if (pEcc->Public(&pSecure->EccContext, NULL, &uCryptUsecs) > 0)
        {
            return(ST3_SEND_KEY);
        }
        NetPrintfVerbose((DEBUG_ENC_PERF, 0, "protossl: SSL Perf (generate public key for client key message) %dms\n",
            uCryptUsecs/1000));
        pSecure->uTimer += uCryptUsecs/1000;

        // encode public key into buffer
        iPublicKeySize = pEcc->PointFinal(pSecure->EccContext, NULL, FALSE, aPublicKey+1, sizeof(aPublicKey)-1);
        aPublicKey[0] = iPublicKeySize;

        // format header
        aHead[0] = SSL3_MSG_CLIENT_KEY;
        aHead[1] = 0;
        aHead[2] = 0;
        aHead[3] = iPublicKeySize+1;

        // setup packet for send
        _ProtoSSLSendPacket(pState, SSL3_REC_HANDSHAKE, aHead, 4, aPublicKey, iPublicKeySize+1);
    }

    // move to next state
    return(pSecure->bSentCert ? ST3_SEND_VERIFY : ST3_SEND_CHANGE);
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLRecvClientKeyExchange

    \Description
        Process ClientKeyExchange handshake packet

    \Input *pState      - module state reference
    \Input *pData       - packet data
    \Input iDataSize    - size of packet data

    \Output
        int32_t         - next state (ST3_RECV_HELLO, ST3_RECV_CHANGE, or ST_FAIL_SETUP on error)

    \Version 10/15/2013 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _ProtoSSLRecvClientKeyExchange(ProtoSSLRefT *pState, const uint8_t *pData, int32_t iDataSize)
{
    SecureStateT *pSecure = pState->pSecure;
    int32_t iCryptKeySize, iPrivKeySize;
    uint8_t strRandomSecret[48];
    X509PrivateKeyT PrivateKey;
    const uint8_t *pCryptKeyData;
    uint16_t uSslVersion;
    const uint8_t *pDataEnd = pData+iDataSize;

    // handle RSA key exchange
    if (pSecure->pCipher->uKey == SSL3_KEY_RSA)
    {
        // read encrypted key data
        iCryptKeySize = _SafeRead16(pData, pDataEnd);
        pCryptKeyData = pData + 2;

        // decode private key and extract key modulus and private exponent
        if ((iPrivKeySize = _CertificateDecodePrivate(pState->pPrivateKey, pState->iPrivateKeyLen, &PrivateKey)) < 0)
        {
            NetPrintf(("protossl: unable to decode private key\n"));
            _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_INTERNAL_ERROR);
            return(ST_FAIL_SETUP);
        }

        // decrypt client premaster secret using private key
        NetPrintfVerbose((pState->iVerbose, 1, "protossl: decrypt client key (iKeySize=%d, iKeyModSize=%d, iKeyExpSize=%d)\n", iPrivKeySize, PrivateKey.Modulus.iObjSize, PrivateKey.PrivateExponent.iObjSize));
        if (CryptRSAInit2(&pSecure->RSAContext, PrivateKey.Modulus.iObjSize, &PrivateKey.PrimeP, &PrivateKey.PrimeQ, &PrivateKey.ExponentP, &PrivateKey.ExponentQ, &PrivateKey.Coefficient))
        {
            _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_INTERNAL_ERROR);
            return(ST_FAIL_SETUP);
        }
        CryptRSAInitSignature(&pSecure->RSAContext, pCryptKeyData, iCryptKeySize);
        CryptRSAEncrypt(&pSecure->RSAContext, 0);

        NetPrintfVerbose((DEBUG_ENC_PERF, 0, "protossl: SSL Perf (rsa decrypt client premaster secret) %dms\n", pSecure->RSAContext.uCryptMsecs));
        pSecure->uTimer += pSecure->RSAContext.uCryptMsecs;

        // copy out and display decrypted client premaster key data
        ds_memcpy(pSecure->PreMasterKey, pSecure->RSAContext.EncryptBlock+iCryptKeySize-sizeof(pSecure->PreMasterKey), sizeof(pSecure->PreMasterKey));
        #if DEBUG_RAW_DATA
        NetPrintMem(pSecure->PreMasterKey, sizeof(pSecure->PreMasterKey), "client premaster key");
        #endif

        // generate random for use in possible roll-back attack handling
        CryptRandGet(strRandomSecret, sizeof(strRandomSecret));
        // validate client TLS version (first two bytes of premaster secret)
        if ((uSslVersion = (pSecure->PreMasterKey[0] << 8) | pSecure->PreMasterKey[1]) != pSecure->uSslClientVersion)
        {
            NetPrintf(("protossl: detected possible roll-back attack; premaster secret tls version %d.%d does not match client-specified version\n",
                pSecure->PreMasterKey[0], pSecure->PreMasterKey[1]));
        }

        /* As per http://tools.ietf.org/html/rfc5246#section-7.4.7.1, a mismatch in the premaster secret tls version
           and the client-specified version should be handled by generating a random premaster secret and continuing
           on.  The random data should be generated unconditionally to prevent possible timing attacks.  This same
           response is also utilized for any detected PKCS#1 padding errors. */
        if ((uSslVersion != pSecure->uSslClientVersion) || !_Pkcs1VerifyRSAES(pSecure->RSAContext.EncryptBlock, iCryptKeySize, sizeof(pSecure->PreMasterKey)))
        {
            ds_memcpy(pSecure->PreMasterKey, strRandomSecret, sizeof(strRandomSecret));
        }

        // build master secret
        _ProtoSSLBuildKey(pState, pSecure->MasterKey, sizeof(pSecure->MasterKey), pSecure->PreMasterKey, sizeof(pSecure->PreMasterKey),
            pSecure->ClientRandom, pSecure->ServerRandom, sizeof(pSecure->ClientRandom), "master secret",
            pSecure->uSslVersion);
    }
    else
    {
        // for ecdhe key exchange we just copy the public key
        pSecure->uPubKeyLength = _SafeRead8(pData++, pDataEnd);
        _SafeReadBytes(pSecure->PubKey, sizeof(pSecure->PubKey), pData, pSecure->uPubKeyLength, pDataEnd);
    }

    // move on to next state
    return(((pState->iClientCertLevel > 0) && pSecure->bRecvCert) ? ST3_RECV_HELLO : ST3_RECV_CHANGE);
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLSendCertificateVerify

    \Description
        Send CertificateVerify handshake packet

    \Input *pState      - module state reference

    \Output
        int32_t         - next state (ST3_SEND_CHANGE or ST3_SEND_FINISH on completion,
                          ST3_SEND_VERIFY if RSA computation is ongoing, or ST_FAIL_SETUP
                          on error)

    \Version 10/30/2013 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _ProtoSSLSendCertificateVerify(ProtoSSLRefT *pState)
{
    SecureStateT *pSecure = pState->pSecure;
    uint32_t uHeadSize;
    uint8_t strHead[16];

    NetPrintfVerbose((DEBUG_MSG_LIST, 0, "protossl: SSL Msg: Send CertificateVerify\n"));

    // set up for encryption of certificate verify message
    if (!pSecure->bSigGenerated)
    {
        uint8_t aSigObj[SSL_SIG_MAX+128]; // must be big enough to hold max hash size plus ASN.1 object encoding (TLS1.2), or a full signature object (TLS1.3+)
        int32_t iPrivKeySize, iHashSize=0;
        X509PrivateKeyT PrivateKey;

        // decode private key and extract key modulus and private exponent
        if ((iPrivKeySize = _CertificateDecodePrivate(pState->pPrivateKey, pState->iPrivateKeyLen, &PrivateKey)) < 0)
        {
            NetPrintf(("protossl: unable to decode private key\n"));
            _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_INTERNAL_ERROR);
            return(ST_FAIL_SETUP);
        }

        // set up buffer with current handshake hash
        if (pSecure->uSslVersion < SSL3_TLS1_2)
        {
            // select signature scheme based on server/client certificate
            pSecure->pSigScheme = _ProtoSSLGetSignatureScheme(pState->pCertificate->iKeyType == ASN_OBJ_ECDSA_KEY ? SSL3_SIGSCHEME_ECDSA_SHA1 : SSL3_SIGSCHEME_RSA_PKCS1_SHA1);
            // generate digest hash
            if (pSecure->pSigScheme->SigAlg.uSigAlg != SSL3_SIGALG_ECDSA)
            {
                iHashSize = _ProtoSSLHandshakeHashGet(pSecure, CRYPTHASH_MD5, aSigObj, SSL3_MAC_MD5);
            }
            iHashSize += _ProtoSSLHandshakeHashGet(pSecure, CRYPTHASH_SHA1, aSigObj+iHashSize, SSL3_MAC_SHA);
        }
        else
        {
            CryptHashTypeE eHashType = _SSL3_HashIdToCrypt[pSecure->pSigScheme->SigAlg.uHashId];
            uint8_t aMessageHash[CRYPTHASH_MAXDIGEST], aTempHash[CRYPTHASH_MAXDIGEST];

            // get message digest hash
            if (pSecure->uSslVersion < SSL3_TLS1_3)
            {
                // get handshake hash using signature hash algorithm
                iHashSize = _ProtoSSLHandshakeHashGet(pSecure, eHashType, aMessageHash, sizeof(aMessageHash));
            }
            else
            {
                // get handshake hash using cipher prf hash algorithm
                iHashSize = _ProtoSSLHandshakeHashGet(pSecure, pSecure->pCipher->uPrfType, aTempHash, sizeof(aTempHash));
                // construct tls1.3 digital signature envelope using signature hash algorithm
                iHashSize = _ProtoSSLGenerateCertificateVerifyHash(aMessageHash, eHashType, aTempHash, iHashSize, pState->bServer, TRUE);
            }

            // encode message digest hash
            if ((pSecure->pSigScheme->uVerifyScheme == SSL3_SIGVERIFY_RSA_PKCS1) && (pSecure->uSslVersion < SSL3_TLS1_3))
            {
                iHashSize = _AsnWriteDigitalHashObject(aSigObj, sizeof(aSigObj), aMessageHash, iHashSize, eHashType);
            }
            else if (pSecure->pSigScheme->uVerifyScheme == SSL3_SIGVERIFY_RSA_PSS)
            {
                iHashSize = _Pkcs1EncodeEMSA_PSS(aSigObj, PrivateKey.Modulus.iObjSize, aMessageHash, iHashSize, eHashType);
            }
            else // ecdsa signatures have no additional encoding
            {
                ds_memcpy_s(aSigObj, sizeof(aSigObj), aMessageHash, iHashSize);
            }
        }

        #if DEBUG_RAW_DATA
        NetPrintMem(aSigObj, iHashSize, "certificate verify hash");
        #endif

        // start async signature generation
        return(_ProtoSSLGenerateSignature(pState, pSecure, pSecure->pSigScheme, aSigObj, iHashSize, &PrivateKey, ST3_SEND_VERIFY));
    }

    // setup the header
    strHead[0] = SSL3_MSG_CERT_VERIFY;
    strHead[1] = 0;

    if (pSecure->uSslVersion < SSL3_TLS1_2)
    {
        strHead[2] = (uint8_t)((pSecure->SigVerify.iSigSize+2)>>8);
        strHead[3] = (uint8_t)((pSecure->SigVerify.iSigSize+2)>>0);
        strHead[4] = (uint8_t)(pSecure->SigVerify.iSigSize>>8);
        strHead[5] = (uint8_t)(pSecure->SigVerify.iSigSize>>0);
        uHeadSize = 6;
    }
    else
    {
        strHead[2] = (uint8_t)((pSecure->SigVerify.iSigSize+4)>>8);
        strHead[3] = (uint8_t)((pSecure->SigVerify.iSigSize+4)>>0);
        strHead[4] = (uint8_t)(pSecure->pSigScheme->uIdent>>8);
        strHead[5] = (uint8_t)(pSecure->pSigScheme->uIdent>>0);
        strHead[6] = (uint8_t)(pSecure->SigVerify.iSigSize>>8);
        strHead[7] = (uint8_t)(pSecure->SigVerify.iSigSize>>0);
        uHeadSize = 8;
    }

    // send the packet
    _ProtoSSLSendPacket(pState, SSL3_REC_HANDSHAKE, strHead, uHeadSize, pSecure->SigVerify.aSigData, pSecure->SigVerify.iSigSize);

    // transition to next state
    return((pSecure->uSslVersion < SSL3_TLS1_3) ? ST3_SEND_CHANGE : ST3_SEND_FINISH);
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLRecvCertificateVerify

    \Description
        Process CertificateVerify handshake packet

    \Input *pState      - module state reference
    \Input *pData       - packet data
    \Input iDataSize    - size of packet data

    \Output
        int32_t         - next state (ST3_RECV_CHANGE, ST3_RECV_FINISH, ST_PROC_ASYNC,
                          or ST_FAIL_SETUP on error)

    \Version 10/30/2013 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _ProtoSSLRecvCertificateVerify(ProtoSSLRefT *pState, const uint8_t *pData, int32_t iDataSize)
{
    SecureStateT *pSecure = pState->pSecure;
    int32_t iCryptKeySize, iHashSize;
    const uint8_t *pCryptKeyData;
    CryptHashTypeE eHashType = CRYPTHASH_NULL;
    const uint8_t *pDataEnd = pData+iDataSize;
    uint8_t aHandshakeHash[CRYPTHASH_MAXDIGEST];
    const SignatureSchemeT *pSigScheme = NULL;
    int32_t iNextState = (pSecure->uSslVersion < SSL3_TLS1_3) ? ST3_RECV_CHANGE : ST3_RECV_FINISH;

    NetPrintfVerbose((DEBUG_MSG_LIST, 0, "protossl: SSL Msg: Process CertificateVerify\n"));

    // get encrypted key data size and offset
    if (pSecure->uSslVersion < SSL3_TLS1_2)
    {
        iCryptKeySize = _SafeRead16(pData, pDataEnd);
        pCryptKeyData = pData + 2;
        pSigScheme = _ProtoSSLGetSignatureScheme((pSecure->Cert.iKeyType == ASN_OBJ_ECDSA_KEY) ? SSL3_SIGSCHEME_ECDSA_SHA1 : SSL3_SIGSCHEME_RSA_PKCS1_SHA1);
    }
    else
    {
        // get signature info
        pSigScheme = _ProtoSSLGetSignatureScheme(_SafeRead16(pData, pDataEnd));
        // validate scheme and key type; as per https://tools.ietf.org/html/rfc8446#section-4.4.3 PKCS1 is not a supported signature scheme in tls1.3+
        if ((pSigScheme == NULL) || ((pSigScheme->uVerifyScheme == SSL3_SIGVERIFY_RSA_PKCS1) && (pSecure->uSslVersion >= SSL3_TLS1_3)))
        {
            NetPrintf(("protossl: unsupported hashid or signature algorithm in CertificateVerify\n"));
            _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_DECODE_ERROR);
            return(ST_FAIL_SETUP);
        }
        // get crypt key size and data
        iCryptKeySize = _SafeRead16(pData+2, pDataEnd);
        pCryptKeyData = pData + 4;
    }

    // get digest hash from signature scheme
    eHashType = _SSL3_HashIdToCrypt[pSigScheme->SigAlg.uHashId];

    // get handshake hash
    if ((pSecure->uSslVersion < SSL3_TLS1_2) && (pSigScheme->SigAlg.uSigAlg != SSL3_SIGALG_ECDSA))
    {
        iHashSize = _ProtoSSLHandshakeHashGet(pSecure, CRYPTHASH_MD5, aHandshakeHash, SSL3_MAC_MD5);
        iHashSize += _ProtoSSLHandshakeHashGet(pSecure, CRYPTHASH_SHA1, aHandshakeHash + iHashSize, SSL3_MAC_SHA);
    }
    else if (pSecure->uSslVersion < SSL3_TLS1_3)
    {
        iHashSize = _ProtoSSLHandshakeHashGet(pSecure, eHashType, aHandshakeHash, sizeof(aHandshakeHash));
    }
    else
    {
        // tls1.3 wraps handshake hash in a digital signature envelope
        uint8_t aHandshakeHash2[CRYPTHASH_MAXDIGEST];
        // get handshake hash
        iHashSize = _ProtoSSLHandshakeHashGet(pSecure, pSecure->pCipher->uPrfType, aHandshakeHash2, sizeof(aHandshakeHash2));
        // construct tls1.3 digital signature envelope
        iHashSize = _ProtoSSLGenerateCertificateVerifyHash(aHandshakeHash, eHashType, aHandshakeHash2, iHashSize, pState->bServer, FALSE);
    }

    // verify the signature; note this is executed iteratively
    return(_ProtoSSLVerifySignature(pState, pSecure, pSigScheme, pCryptKeyData, iCryptKeySize, aHandshakeHash, iHashSize, pSecure->uSslVersion >= SSL3_TLS1_2 ? eHashType : CRYPTHASH_NULL, iNextState));
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLSendChangeCipherSpec

    \Description
        Send ChangeCipherSpec handshake packet

    \Input *pState      - module state reference

    \Output
        int32_t         - next state (ST3_CHANGE, ST3_SEND_FINISH)

    \Notes
        Applies to TLS1.2 and prior only

    \Version 10/15/2013 (jbrookes) Updated to handle client and server
*/
/********************************************************************************F*/
static int32_t _ProtoSSLSendChangeCipherSpec(ProtoSSLRefT *pState)
{
    SecureStateT *pSecure = pState->pSecure;
    uint8_t strHead[4];

    NetPrintfVerbose((DEBUG_MSG_LIST, 0, "protossl: SSL Msg: Send ChangeCipherSpec\n"));

    // generate the shared secret if necessary
    if (!pState->bServer && !pSecure->bSessionResume && !_ProtoSSLGenerateEccSharedSecret(pState))
    {
        return(ST3_SEND_CHANGE);
    }

    // build key material if we haven't already
    if (pSecure->pServerKey == NULL)
    {
        _ProtoSSLBuildKeyMaterial(pState);
    }

    // initialize write cipher
    if (pSecure->pCipher->uEnc == SSL3_ENC_AES)
    {
        CryptAesInit(&pSecure->WriteAes, pState->bServer ? pSecure->pServerKey : pSecure->pClientKey, pSecure->pCipher->uLen, CRYPTAES_KEYTYPE_ENCRYPT,
            pState->bServer ? pSecure->pServerInitVec : pSecure->pClientInitVec);
    }
    if (pSecure->pCipher->uEnc == SSL3_ENC_GCM)
    {
        CryptGcmInit(&pSecure->WriteGcm, pState->bServer ? pSecure->pServerKey : pSecure->pClientKey, pSecure->pCipher->uLen);
    }
    if (pSecure->pCipher->uEnc == SSL3_ENC_CHACHA)
    {
        CryptChaChaInit(&pSecure->WriteChaCha, pState->bServer ? pSecure->pServerKey : pSecure->pClientKey, pSecure->pCipher->uLen);
    }

    // mark as cipher change
    strHead[0] = 1;
    _ProtoSSLSendPacket(pState, SSL3_REC_CIPHER, strHead, 1, NULL, 0);

    // reset the sequence number
    pSecure->uSendSeqn = 0;
    return(ST3_SEND_FINISH);
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLRecvChangeCipherSpec

    \Description
        Process ChangeCipherSpec handshake packet

    \Input *pState      - module state reference
    \Input *pData       - packet data
    \Input iDataSize    - size of packet data

    \Output
        int32_t         - next state (ST3_RECV_CHANGE, ST3_RECV_FINISH)

    \Notes
        Applies to TLS1.2 and prior only

    \Version 10/15/2013 (jbrookes) Updated to handle client and server
*/
/********************************************************************************F*/
static int32_t _ProtoSSLRecvChangeCipherSpec(ProtoSSLRefT *pState, const uint8_t *pData, int32_t iDataSize)
{
    SecureStateT *pSecure = pState->pSecure;
    const uint8_t *pDataEnd = pData + iDataSize;

    NetPrintfVerbose((DEBUG_MSG_LIST, 0, "protossl: SSL Msg: Process ChangeCipherSpec\n"));

    // validate ccs message
    if ((iDataSize != 1) || (_SafeRead8(pData, pDataEnd) != 1))
    {
        NetPrintf(("protossl: invalid ChangeCipherSpec message\n"));
        _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_DECODE_ERROR);
        pState->iClosed = 1;
        _ProtoSSLRecvReset(pSecure);
        pState->iState = ST_FAIL_SETUP;
    }

    // generate the shared secret if necessary
    if (pState->bServer && !pSecure->bSessionResume && !_ProtoSSLGenerateEccSharedSecret(pState))
    {
        return(ST3_RECV_CHANGE);
    }

    // build key material if we haven't already
    if (pSecure->pServerKey == NULL)
    {
        _ProtoSSLBuildKeyMaterial(pState);
    }

    // initialize read cipher
    if (pSecure->pCipher->uEnc == SSL3_ENC_AES)
    {
        CryptAesInit(&pSecure->ReadAes, pState->bServer ? pSecure->pClientKey : pSecure->pServerKey, pSecure->pCipher->uLen, CRYPTAES_KEYTYPE_DECRYPT,
            pState->bServer ? pSecure->pClientInitVec : pSecure->pServerInitVec);
    }
    if (pSecure->pCipher->uEnc == SSL3_ENC_GCM)
    {
        CryptGcmInit(&pSecure->ReadGcm, pState->bServer ? pSecure->pClientKey : pSecure->pServerKey, pSecure->pCipher->uLen);
    }
    if (pSecure->pCipher->uEnc == SSL3_ENC_CHACHA)
    {
        CryptChaChaInit(&pSecure->ReadChaCha, pState->bServer ? pSecure->pClientKey : pSecure->pServerKey, pSecure->pCipher->uLen);
    }

    // reset sequence number
    pSecure->uRecvSeqn = 0;

    // just gobble packet -- we assume next state is crypted regardless
    return(ST3_RECV_FINISH);
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLSendFinished

    \Description
        Send Finished handshake packet

    \Input *pState      - module state reference

    \Output
        int32_t         - next state (ST3_RECV_CHANGE, ST3_RECV_HELLO,
                                      ST3_RECV_FINISH, ST3_SECURE)

    \Version 10/23/2013 (jbrookes) Rewritten to handle client and server
*/
/********************************************************************************F*/
static int32_t _ProtoSSLSendFinished(ProtoSSLRefT *pState)
{
    uint8_t strHead[4];
    uint8_t strBody[256];
    SecureStateT *pSecure = pState->pSecure;
    static const struct SSLFinished _SendFinished[2] = {
        { "client finished", {ST3_RECV_CHANGE, ST3_SECURE } },
        { "server finished", {ST3_SECURE, ST3_RECV_CHANGE } },
    };
    const struct SSLFinished *pFinished = &_SendFinished[pState->bServer];
    int32_t iNextState = ST3_SECURE;

    NetPrintfVerbose((DEBUG_MSG_LIST, 0, "protossl: SSL Msg: SendFinished\n"));

    // do finish hash, save size in packet header
    strHead[3] = (uint8_t)_ProtoSSLGenerateFinishHash(strBody, pSecure, pFinished->strLabel, pState->bServer, TRUE);

    // setup the header
    strHead[0] = SSL3_MSG_FINISHED;
    strHead[1] = 0;
    strHead[2] = 0;

    // all sends from here on out are secure
    pSecure->bSendSecure = TRUE;

    // setup packet for send
    _ProtoSSLSendPacket(pState, SSL3_REC_HANDSHAKE, strHead, sizeof(strHead), strBody, strHead[3]);

    // tls1.3 handshake processing
    if (pSecure->uSslVersion >= PROTOSSL_VERSION_TLS1_3)
    {
        if (pState->bServer)
        {
            // save handshake hash for building application secrets
            _ProtoSSLHandshakeHashGet(pSecure, pSecure->pCipher->uPrfType, pSecure->aFinishHash, sizeof(pSecure->aFinishHash));
        }
        else
        {
            // switch to application keys; this must come after _ProtoSSLSendPacket as the server expects the Finished packet to be sent using the handshake key
            _ProtoSSLBuildApplicationKey(pState, pSecure);
        }
    }

    // determine next state
    if (pSecure->uSslVersion < PROTOSSL_VERSION_TLS1_3)
    {
        // next state will be secure mode (handshaking complete) or recv cipher change depending on client/server and if resuming or not
        iNextState = pFinished->uNextState[pSecure->bSessionResume];
    }
    else if (pState->bServer)
    {
        // server will expect either Finished or Certificate if requesting a client cert
        iNextState = (pState->iClientCertLevel != 0) ? ST3_RECV_HELLO : ST3_RECV_FINISH;
    }
    return(iNextState);
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLRecvFinished

    \Description
        Process Finished handshake packet

    \Input *pState      - module state reference
    \Input *pData       - packet data
    \Input iDataSize    - size of packet data

    \Output
    int32_t             - next state (ST3_SEND_CHANGE, ST3_SEND_CERT, ST3_SEND_FINISH,
                          ST3_SECURE, or ST_FAIL_SETUP on error)

    \Version 10/23/2013 (jbrookes) Rewritten to handle client and server
*/
/********************************************************************************F*/
static int32_t _ProtoSSLUpdateRecvFinished(ProtoSSLRefT *pState, const uint8_t *pData, int32_t iDataSize)
{
    SecureStateT *pSecure = pState->pSecure;
    uint8_t aMacFinal[CRYPTHASH_MAXDIGEST];
    int32_t iMacSize, iNextState = ST3_SECURE;
    static const struct SSLFinished _RecvFinished[2] = {
        { "server finished", {ST3_SECURE, ST3_SEND_CHANGE } },
        { "client finished", {ST3_SEND_CHANGE, ST3_SECURE } },
    };
    const struct SSLFinished *pFinished = &_RecvFinished[pState->bServer];
    const uint8_t *pDataEnd = pData + iDataSize;

    NetPrintfVerbose((DEBUG_MSG_LIST, 0, "protossl: SSL Msg: Process RecvFinished\n"));

    // calculate the finish verification hash
    iMacSize = _ProtoSSLGenerateFinishHash(aMacFinal, pSecure, pFinished->strLabel, pState->bServer, FALSE);

    // verify the hash
    if (memcmp(aMacFinal, pData, DS_MIN(iMacSize, pDataEnd-pData)))
    {
        NetPrintf(("protossl: finish hash mismatch; failed setup\n"));
        #if DEBUG_RAW_DATA
        NetPrintMem(aMacFinal, iMacSize, "aMacFinal");
        NetPrintMem(pData, iMacSize, "pData");
        #endif  
        _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_DECRYPT_ERROR);
        return(ST_FAIL_SETUP);
    }

    // save session (tls1.2 and previous only)
    if (pSecure->uSslVersion <= PROTOSSL_VERSION_TLS1_2)
    {
        _SessionHistoryAdd(pSecure, pState->strHost, SockaddrInGetPort(&pState->PeerAddr), NULL, pSecure->SessionId, sizeof(pSecure->SessionId), pSecure->MasterKey);
    }

    // tls1.3 handshake processing
    if (pSecure->uSslVersion >= PROTOSSL_VERSION_TLS1_3)
    {
        if (pState->bServer)
        {
            // switch to application keys
            _ProtoSSLBuildApplicationKey(pState, pSecure);
        }
        else
        {
            // update hash state with received Finished packet from server (which hasn't been processed yet)
            _ProtoSSLRecvHandshakeFinish(pState);
            // save current handshake hash
            _ProtoSSLHandshakeHashGet(pSecure, pSecure->pCipher->uPrfType, pSecure->aFinishHash, sizeof(pSecure->aFinishHash));
        }
    }
    
    // determine next state
    if (pSecure->uSslVersion < PROTOSSL_VERSION_TLS1_3)
    {
        // next state will be secure mode (handshaking complete) or send cipher change depending on client/server and if resuming or not
        iNextState = pFinished->uNextState[pSecure->bSessionResume];
    }
    else if (!pState->bServer)
    {
        // client will send either Finished or Certificate if a client cert was requested by the server
        iNextState = (pState->iClientCertLevel != 0) ? ST3_SEND_CERT : ST3_SEND_FINISH;
    }
    return(iNextState);
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLSendHelloRequest

    \Description
        Send HelloRequest handshake packet

    \Input *pState      - module state reference

    \Output
        int32_t         - next state (ST3_RECV_HELLO)

    \Version 03/28/2018 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _ProtoSSLSendHelloRequest(ProtoSSLRefT *pState)
{
    uint8_t strHead[4];

    NetPrintfVerbose((DEBUG_MSG_LIST, 0, "protossl: SSL Msg: Send HelloRequest\n"));

    // setup the header
    strHead[0] = SSL3_MSG_HELLO_REQUEST;
    strHead[1] = 0;
    strHead[2] = 0;
    strHead[3] = 0;

    // setup packet for send
    _ProtoSSLSendPacket(pState, SSL3_REC_HANDSHAKE, strHead, sizeof(strHead), strHead, 0);

    // set up to receive ClientHello
    return(ST3_RECV_HELLO);
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLRecvHelloRequest

    \Description
        Receive HelloRequest handshake packet; we respond with a no_renegotation
        warning alert, or unexpected_message if the connection is TLS 1.3.

    \Input *pState      - module state reference
    \Input *pData       - packet data
    \Input iDataSize    - size of packet data

    \Output
        int32_t         - next state (current state)

    \Version 03/28/2018 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _ProtoSSLRecvHelloRequest(ProtoSSLRefT *pState, const uint8_t *pData, int32_t iDataSize)
{
    SecureStateT *pSecure = pState->pSecure;

    NetPrintfVerbose((DEBUG_MSG_LIST, 0, "protossl: SSL Msg: Process HelloRequest\n"));

    if (pSecure->uSslVersion < PROTOSSL_VERSION_TLS1_3)
    {
        // respond with no_renegotation alert; if we were to implement renegotation, we'd move to ST3_SEND_HELLO
        _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_WARNING, SSL3_ALERT_DESC_NO_RENEGOTIATION);
    }
    else
    {
        // hello request is not a valid tls 1.3 message
        _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_UNEXPECTED_MESSAGE);
    }

    return(pState->iState);
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLRecvNewSessionTicket

    \Description
        Process NewSessionTicket handshake packet; see
        https://tools.ietf.org/html/rfc8446#section-4.6.1

    \Input *pState      - module state reference
    \Input *pData       - packet data
    \Input iDataSize    - size of packet data

    \Output
        int32_t         - next state (ST3_SECURE)

    \Version 03/17/2017 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _ProtoSSLRecvNewSessionTicket(ProtoSSLRefT *pState, const uint8_t *pData, int32_t iDataSize)
{
    const uint8_t *pDataEnd = pData + iDataSize;
    SecureStateT *pSecure = pState->pSecure;
    SessionTicketT SessTick;
    CryptHashTypeE eHashType = pSecure->pCipher->uPrfType;
    int32_t iHashLen = CryptHashGetSize(eHashType);

    NetPrintfVerbose((DEBUG_MSG_LIST, 0, "protossl: SSL Msg: Process NewSessionTicket\n"));

    // initialize session ticket memory
    ds_memset(&SessTick, 0, sizeof(SessTick));
    // save receipt time
    SessTick.uRecvTime = time(NULL);
    // save resumption key
    ds_memcpy_s(SessTick.aResumeKey, sizeof(SessTick.aResumeKey), pSecure->pResumeSecret, iHashLen);
    // save hash type
    SessTick.eHashType = eHashType;

    // parse required session ticket fields
    SessTick.uLifetime = _SafeRead32(pData, pDataEnd);
    pData += 4;
    SessTick.uAgeAdd = _SafeRead32(pData, pDataEnd);
    pData += 4;

    // copy ticket nonce
    SessTick.uNonceLen = _SafeRead8(pData, pDataEnd);
    pData += 1;
    _SafeReadBytes(SessTick.aTicketNonce, sizeof(SessTick.aTicketNonce), pData, SessTick.uNonceLen, pDataEnd);
    pData += SessTick.uNonceLen;

    // copy ticket length
    SessTick.uTickLen = _SafeRead16(pData, pDataEnd);
    pData += 2;

    // make sure we have enough space
    if (SessTick.uTickLen > sizeof(SessTick.aTicketData))
    {
        NetPrintf(("protossl: session ticket too large for session history buffer\n"));
        return(ST3_SECURE);
    }

    // copy ticket data
    _SafeReadBytes(SessTick.aTicketData, sizeof(SessTick.aTicketData), pData, SessTick.uTickLen, pDataEnd);
    pData += SessTick.uTickLen;

    // parse extensions length, if present
    if (pData <= (pDataEnd-2))
    {
        SessTick.uExtnLen = _SafeRead16(pData, pDataEnd);
    }

    NetPrintfVerbose((pState->iVerbose, 0, "protossl: ticket_sni: %s:%d, ticket_lifetime=%d, ticket_age_add=0x%08x, nonce_len=%d, ticket_len=%d, extn_len=%d\n",
        pState->strHost, SockaddrInGetPort(&pState->PeerAddr), SessTick.uLifetime, SessTick.uAgeAdd, SessTick.uNonceLen, SessTick.uTickLen, SessTick.uExtnLen));

    // add ticket to session history
    _SessionHistoryAdd(pSecure, pState->strHost, SockaddrInGetPort(&pState->PeerAddr), &SessTick, NULL, 0, NULL);

    return(ST3_SECURE);
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLRecvHandshakeValidate

    \Description
        Validate handshake message transition is permissible; as per
        https://tools.ietf.org/html/rfc8446#section-4: "a peer which receives a
        handshake message in an unexpected order MUST abort the handshake
        with an "unexpected_message" alert."

    \Input *pState      - protossl state
    \Input *pSecure     - secure state
    \Input iNxtMsg      - next message

    \Output
        int32_t         - true if validated, else false

    \Notes
        This function validates that a given handshake state transition is possible
        within the spec, but it does not validate that the transition is correct
        given the specific set of parameters (ciphers/signature algorithms/etc)
        selected.  Such validation is left up to the specific handshake handler.

    \Version 05/02/2019 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _ProtoSSLRecvHandshakeValidate(ProtoSSLRefT *pState, SecureStateT *pSecure, int32_t iNxtMsg)
{
    static const SSLHandshakeMapT *_aHandshakeMaps[2][2] =
    {
        { _SSL3_ClientRecvMsgMap, _SSL3_ClientRecvMsgMap_13 },
        { _SSL3_ServerRecvMsgMap, _SSL3_ServerRecvMsgMap_13 }
    };
    uint32_t uMapVersIdx = (pSecure->uSslVersion >= PROTOSSL_VERSION_TLS1_3) ? 1 : 0;
    const SSLHandshakeMapT *pMap;
    int32_t iCount;

    for (iCount = 0, pMap = _aHandshakeMaps[pState->bServer][uMapVersIdx]; pMap[iCount].iCurMsg != -1; iCount += 1)
    {
        if (pSecure->iCurMsg != pMap[iCount].iCurMsg)
        {
            continue;
        }
        if (iNxtMsg != pMap[iCount].iNxtMsg)
        {
            continue;
        }
        break;
    }

    NetPrintfVerbose((DEBUG_MSG_LIST || (pMap[iCount].iCurMsg == -1), 0, "protossl: %s handshake message state transition %s(%d)->%s(%d)\n", (pMap[iCount].iCurMsg != -1) ? "validated" : "invalid",
        _ProtoSSLRecvGetHandshakeTypeName(pSecure->iCurMsg), pSecure->iCurMsg,
        _ProtoSSLRecvGetHandshakeTypeName(iNxtMsg), iNxtMsg));

    return(pMap[iCount].iCurMsg != -1);
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLRecvHandshake

    \Description
        Decode ssl handshake packet, validate message flow

    \Input *pState      - protossl state
    \Input *pSecure     - secure state
    \Input *pMsgType    - [out] handshake message type

    \Output
        uint8_t *       - pointer to handshake packet start, or NULL if error

    \Version 11/10/2005 (gschaefer)
*/
/********************************************************************************F*/
static const uint8_t *_ProtoSSLRecvHandshake(ProtoSSLRefT *pState, SecureStateT *pSecure, uint8_t *pMsgType)
{
    uint8_t *pRecv = pSecure->RecvData+pSecure->iRecvHshkProg;
    
    // get message type
    *pMsgType = pRecv[0];

    // make sure next message is possible in handshake flow
    if (!_ProtoSSLRecvHandshakeValidate(pState, pSecure, *pMsgType))
    {
        return(NULL);
    }

    // update current message
    pSecure->iCurMsg = *pMsgType;

    // point to data
    return(pRecv+4);
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLRecvHandshakeFinish

    \Description
        Complete processing of handshake packet, including computation of
        handshake hash.

    \Input *pState      - module state reference

    \Version 03/16/2012 (jbrookes)
*/
/********************************************************************************F*/
static void _ProtoSSLRecvHandshakeFinish(ProtoSSLRefT *pState)
{
    SecureStateT *pSecure = pState->pSecure;

    // if this is a handshake packet and we haven't already processed it
    if ((pSecure->RecvHead[0] == SSL3_REC_HANDSHAKE) && (pSecure->iRecvSize > 0) && (pSecure->iRecvSize >= pSecure->iRecvHshkSize))
    {
        // keep a running hash of received data for verify/finish hashes
        _ProtoSSLHandshakeHashUpdate(pSecure, pSecure->RecvData+pSecure->iRecvHshkProg, pSecure->iRecvHshkSize, "recv");
        // consume the packet
        pSecure->iRecvHshkProg += pSecure->iRecvHshkSize;
        pSecure->iRecvHshkSize = 0;
    }

    // see if all the data was consumed
    if (pSecure->iRecvHshkProg == pSecure->iRecvSize)
    {
        _ProtoSSLRecvReset(pSecure);
    }
}

/*
    main update loop
*/

/*F********************************************************************************/
/*!
    \Function _ProtoSSLUpdateAsync

    \Description
        Perform iterative operations

    \Input *pState      - module state reference
    \Input *pSecure     - secure state reference

    \Output
        int32_t         - one if async operation occurred, else zero

    \Version 02/15/2018 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _ProtoSSLUpdateAsync(ProtoSSLRefT *pState, SecureStateT *pSecure)
{
    #if DEBUG_ENC_PERF
    uint64_t uTickUsec = NetTickUsec();
    #endif
    int32_t iResult;
    // not in async state, return
    if (pState->iState != ST3_PROC_ASYNC)
    {
        return(0);
    }
    // execute async op and check for completion
    iResult = pState->AsyncInfo.pAsyncOp(pState);
    #if DEBUG_ENC_PERF
    NetPrintf(("protossl: async op took %qd us\n", NetTickDiff(NetTickUsec(), uTickUsec)));
    #endif
    if (iResult == 0)
    {
        pState->iState = pState->AsyncInfo.iNextState;
        pState->AsyncInfo.iNextState = 0;
        return(0);
    }
    else if (iResult < 0)
    {
        pState->iState = pState->AsyncInfo.iFailState;
        pState->AsyncInfo.iNextState = 0;
        _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, pState->AsyncInfo.iFailAlert);
        return(0);
    }
    // continuing to execute async op
    return(ST3_PROC_ASYNC);
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLUpdateSetAsyncState

    \Description
        Set up for 'async' execution of pAsyncOp.  Asynchronous in this
        case means iterative execution from the main update loop.

    \Input *pState      - module state
    \Input *pAsyncOp    - function to execute asynchronously
    \Input iNextState   - state to transition to on success
    \Input iFailState   - state to transition to on failure
    \Input iFailAlert   - alert to send on failure

    \Output
        int32_t         - updated state (ST3_PROC_ASYNC)

    \Version 02/16/2018 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _ProtoSSLUpdateSetAsyncState(ProtoSSLRefT *pState, AsyncOpT *pAsyncOp, int32_t iNextState, int32_t iFailState, int32_t iFailAlert)
{
    pState->iState = ST3_PROC_ASYNC;
    pState->AsyncInfo.iNextState = iNextState;
    pState->AsyncInfo.iFailState = iFailState;
    pState->AsyncInfo.iFailAlert = iFailAlert;
    pState->AsyncInfo.pAsyncOp = pAsyncOp;
    return(_ProtoSSLUpdateAsync(pState, pState->pSecure));
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLUpdateSend

    \Description
        Send data on a secure connection

    \Input *pState      - module state reference
    \Input *pSecure     - secure state reference

    \Output
        int32_t         - one if data was sent, else zero

    \Version 03/08/2002 (gschaefer)
*/
/********************************************************************************F*/
static int32_t _ProtoSSLUpdateSend(ProtoSSLRefT *pState, SecureStateT *pSecure)
{
    // handle send states if output buffer is empty
    if (pSecure->iSendProg == pSecure->iSendSize)
    {
        // deal with send states
        if (pState->iState == ST3_SEND_HELLO)
        {
            pState->iState = pState->bServer ? _ProtoSSLSendServerHello(pState) : _ProtoSSLSendClientHello(pState);
        }
        else if (pState->iState == ST3_SEND_HELLO_RETRY)
        {
            pState->iState = _ProtoSSLSendHelloRetryRequest(pState);
        }
        else if (pState->iState == ST3_SEND_EXTN)
        {
            pState->iState = _ProtoSSLSendEncryptedExtensions(pState);
        }
        else if (pState->iState == ST3_SEND_CERT)
        {
            pState->iState = _ProtoSSLSendCertificate(pState);
        }
        else if (pState->iState == ST3_SEND_CERT_REQ)
        {
            pState->iState = _ProtoSSLSendCertificateRequest(pState);
        }
        else if (pState->iState == ST3_SEND_DONE)
        {
            pState->iState = _ProtoSSLSendServerHelloDone(pState);
        }
        else if (pState->iState == ST3_SEND_KEY)
        {
            pState->iState = pState->bServer ? _ProtoSSLSendServerKeyExchange(pState) : _ProtoSSLSendClientKeyExchange(pState);
        }
        else if (pState->iState == ST3_SEND_VERIFY)
        {
            pState->iState = _ProtoSSLSendCertificateVerify(pState);
        }
        else if (pState->iState == ST3_SEND_CHANGE)
        {
            pState->iState = _ProtoSSLSendChangeCipherSpec(pState);
        }
        else if (pState->iState == ST3_SEND_FINISH)
        {
            pState->iState = _ProtoSSLSendFinished(pState);
        }
        else if (pState->iState == ST3_SEND_HELLO_REQUEST)
        {
            pState->iState = _ProtoSSLSendHelloRequest(pState);
        }
    }

    // send any queued data
    return(_ProtoSSLSendSecure(pState, pState->pSecure));
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLUpdateRecvHandshake

    \Description
        Process a handshake message

    \Input *pState      - module state reference
    \Input *pSecure     - secure state reference

    \Output
            uint32_t    - FALSE if there was an error, else TRUE

    \Version 11/14/2017 (jbrookes)
*/
/********************************************************************************F*/
static uint32_t _ProtoSSLUpdateRecvHandshake(ProtoSSLRefT *pState, SecureStateT *pSecure)
{
    uint8_t bUnhandledMsg = FALSE, uMsgType;
    const uint8_t *pData;
    int32_t iDataSize;

    // make sure it's a handshake message
    if (pSecure->RecvHead[0] != SSL3_REC_HANDSHAKE)
    {
        /* as per https://tools.ietf.org/html/rfc8446#section-5, if a TLS implementation receives an unexpected record type,
           it MUST terminate the connection with an "unexpected_message" alert */
        NetPrintf(("protossl: received unexpected record %s (%d) when expecting a handshake record\n", _ProtoSSLRecvGetRecordTypeName(pSecure->RecvHead[0]), pSecure->RecvHead[0]));
        _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_UNEXPECTED_MESSAGE);
        return(FALSE);
    }

    // calculate handshake packet size
    if (pSecure->iRecvHshkSize == 0)
    {
        pData = pSecure->RecvData + pSecure->iRecvHshkProg;
        pSecure->iRecvHshkSize = ((pData[1]<<16)|(pData[2]<<8)|(pData[3]<<0)) + SSL3_MSG_HEADER_SIZE;
        // make sure packet fits in our buffer
        if ((pSecure->iRecvHshkProg+pSecure->iRecvHshkSize) > (int32_t)sizeof(pSecure->RecvData))
        {
            NetPrintf(("protossl: _ProtoSSLUpdateRecvHandshake: packet at base %d is too long (%d vs %d)\n", pSecure->iRecvHshkProg,
                pSecure->iRecvHshkSize, sizeof(pSecure->RecvData)-pSecure->iRecvHshkProg));
            _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_DECODE_ERROR);
            return(FALSE);
        }
    }

    // make sure we have the entire packet before proceeding
    if (pSecure->iRecvSize < pSecure->iRecvHshkSize)
    {
        // receive next packet header
        pSecure->iRecvHead = 0;
        pSecure->bRecvProc = FALSE;
        return(TRUE);
    }

    // size of handshake data is handshake size minus header size
    iDataSize = pSecure->iRecvHshkSize - SSL3_MSG_HEADER_SIZE;

    // get handshake message and ensure it is valid in the handshake state machine
    if ((pData = _ProtoSSLRecvHandshake(pState, pSecure, &uMsgType)) == NULL)
    {
        _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_UNEXPECTED_MESSAGE);
        return(FALSE);
    }

    // process handshake message based on state
    if (pState->iState == ST3_RECV_HELLO)
    {
        if (uMsgType == SSL3_MSG_CLIENT_HELLO)
        {
            pState->iState = _ProtoSSLRecvClientHello(pState, pData, iDataSize);
        }
        else if (uMsgType == SSL3_MSG_SERVER_HELLO)
        {
            pState->iState = _ProtoSSLRecvServerHello(pState, pData, iDataSize);
        }
        else if (uMsgType == SSL3_MSG_ENCRYPTED_EXTENSIONS)
        {
            pState->iState = _ProtoSSLRecvEncryptedExtensions(pState, pData, iDataSize);
        }
        else if (uMsgType == SSL3_MSG_CERTIFICATE)
        {
            pState->iState = _ProtoSSLRecvCertificate(pState, pData, iDataSize);
        }
        else if (uMsgType == SSL3_MSG_CERT_REQ)
        {
            pState->iState = _ProtoSSLRecvCertificateRequest(pState, pData, iDataSize);
        }
        else if (uMsgType == SSL3_MSG_CERT_VERIFY)
        {
            pState->iState = _ProtoSSLRecvCertificateVerify(pState, pData, iDataSize);
        }
        else if (uMsgType == SSL3_MSG_CLIENT_KEY)
        {
            pState->iState = _ProtoSSLRecvClientKeyExchange(pState, pData, iDataSize);
        }
        else if (uMsgType == SSL3_MSG_SERVER_KEY)
        {
            pState->iState = _ProtoSSLRecvServerKeyExchange(pState, pData, iDataSize);
        }
        else if (uMsgType == SSL3_MSG_SERVER_DONE)
        {
            pState->iState = _ProtoSSLRecvServerHelloDone(pState, pData, iDataSize);
        }
        else
        {
            bUnhandledMsg = TRUE;
        }
    }
    else if (pState->iState == ST3_RECV_FINISH)
    {
        if (uMsgType == SSL3_MSG_FINISHED)
        {
            pState->iState = _ProtoSSLUpdateRecvFinished(pState, pData, iDataSize);
        }
        else
        {
            bUnhandledMsg = TRUE;
        }
    }
    else if (pState->iState == ST3_SECURE)
    {
        // handle post-finish handshake messages
        if (uMsgType == SSL3_MSG_HELLO_REQUEST)
        {
            pState->iState = _ProtoSSLRecvHelloRequest(pState, pData, iDataSize);
        }
        else if (uMsgType == SSL3_MSG_NEW_SESSION_TICKET)
        {
            pState->iState = _ProtoSSLRecvNewSessionTicket(pState, pData, iDataSize);
        }
        // finish here since we won't in _ProtoSSLUpdateRecvPacket()
        _ProtoSSLRecvHandshakeFinish(pState);
    }
    else
    {
        bUnhandledMsg = TRUE;
    }

    // fatal alert if message was unhandled
    if (bUnhandledMsg)
    {
        NetPrintf(("protossl: unhandled handshake message %s(%d) in state %d\n", _ProtoSSLRecvGetHandshakeTypeName(uMsgType), uMsgType, pState->iState));
        _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_UNEXPECTED_MESSAGE);
    }

    return(!bUnhandledMsg);
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLUpdateRecvPacket

    \Description
        Process a received packet

    \Input *pState      - module state reference
    \Input *pSecure     - secure state reference

    \Version 11/14/2017 (jbrookes)
*/
/********************************************************************************F*/
static void _ProtoSSLUpdateRecvPacket(ProtoSSLRefT *pState, SecureStateT *pSecure)
{
    int32_t iState = pState->iState;

    if (pSecure->RecvHead[0] == SSL3_REC_ALERT)
    {
        _ProtoSSLRecvAlert(pState, pSecure);
    }
    else if ((pSecure->RecvHead[0] == SSL3_REC_CIPHER) && (pState->iState == ST3_RECV_CHANGE))
    {
        if ((pState->iState = _ProtoSSLRecvChangeCipherSpec(pState, pSecure->RecvData+pSecure->iRecvHshkProg, pSecure->iRecvSize)) != ST3_RECV_CHANGE)
        {
            pSecure->iRecvHshkProg = pSecure->iRecvSize;
        }
    }
    else if (!_ProtoSSLUpdateRecvHandshake(pState, pSecure))
    {
        pState->iClosed = 1;
        _ProtoSSLRecvReset(pSecure);
        pState->iState = (pState->iState < ST3_SECURE) ? ST_FAIL_SETUP : ST_FAIL_SECURE;
    }

    // output final crypto timing, if we have just reached secure state
    if ((iState != pState->iState) && (pState->iState == ST3_SECURE) && (pSecure->uTimer > 0))
    {
        NetPrintfVerbose((pState->iVerbose, 0, "protossl: SSL Perf (setup) %dms\n", pSecure->uTimer));
        pSecure->uTimer = 0;
    }

    /* finish recv handshake processing: due to the fact that the recv hello state handles which state we are in based on the
       packet data we want to move forward regardless.  when we are not in the recv hello state we want to make sure to to only
       move to the next state when the state changes to allow for any additional processing that happens over multiple frames. */
    if ((pState->iState == ST3_RECV_HELLO) || (iState != pState->iState))
    {
        _ProtoSSLRecvHandshakeFinish(pState);
    }
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLUpdateRecvValidate

    \Description
        Validate received packet

    \Input *pState      - module state reference
    \Input *pSecure     - secure state reference

    \Version 11/14/2017 (jbrookes)
*/
/********************************************************************************F*/
static void _ProtoSSLUpdateRecvValidate(ProtoSSLRefT *pState, SecureStateT *pSecure)
{
    /* validate the format version, and make sure it is a 3.x protocol version.  we make an assumption here that
       the 3.x packet format will not change in future revisions to the TLS protocol (this was confirmed with the
       1.3 version of the protocol).  validation of the specific protocol version is handled during handshaking. */
    if (pSecure->RecvHead[1] == (SSL3_VERSION>>8))
    {
        uint32_t uRecvSize = (pSecure->RecvHead[3]<<8)|(pSecure->RecvHead[4]<<0);
        // check data length to make sure it is valid; as per http://tools.ietf.org/html/rfc5246#section-6.2.1: The length MUST NOT exceed 2^14+2^11
        if (uRecvSize > SSL_RCVMAX_PACKET)
        {
            NetPrintf(("protossl: received oversized packet (%d/%d); terminating connection\n", uRecvSize, SSL_RCVMAX_PACKET));
            _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_RECORD_OVERFLOW);
            pState->iClosed = 1;
            _ProtoSSLRecvReset(pSecure);
            pState->iState = (pState->iState < ST3_SECURE) ? ST_FAIL_SETUP : ST_FAIL_SECURE;
        }
        // save data offset
        pSecure->iRecvBase = pSecure->iRecvSize;
        // add in the data length
        pSecure->iRecvSize += uRecvSize;
        // make sure it doesn't exceed our buffer
        if (pSecure->iRecvSize > (int32_t)sizeof(pSecure->RecvData))
        {
            NetPrintf(("protossl: packet length of %d is too large\n", pSecure->iRecvSize));
            _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, SSL3_ALERT_DESC_RECORD_OVERFLOW);
            pState->iClosed = 1;
            _ProtoSSLRecvReset(pSecure);
            pState->iState = (pState->iState < ST3_SECURE) ? ST_FAIL_SETUP : ST_FAIL_SECURE;
        }
    }
    else
    {
        if ((pSecure->RecvHead[0] == 0x80) && (pSecure->RecvHead[2] == 1))
        {
            NetPrintf(("protossl: received %d byte SSLv2 ClientHello offering SSLv%d.%d; disconnecting\n",
                pSecure->RecvHead[1], pSecure->RecvHead[3], pSecure->RecvHead[4]));
            pState->iState = ST_FAIL_CONN_SSL2;
        }
        else
        {
            NetPrintf(("protossl: received what appears to be a non-SSL connection attempt; disconnecting\n"));
            pState->iState = ST_FAIL_CONN_NOTSSL;
        }
        pState->iClosed = 1;
        _ProtoSSLRecvReset(pSecure);
    }
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLUpdateRecv

    \Description
        Receive data on a secure connection

    \Input *pState      - module state reference
    \Input *pSecure     - secure state reference

    \Output
        int32_t         - 1=data was received, else 0

    \Version 03/08/2002 (gschaefer)
*/
/********************************************************************************F*/
static int32_t _ProtoSSLUpdateRecv(ProtoSSLRefT *pState, SecureStateT *pSecure)
{
    int32_t iResult, iXfer = 0;

    // receive ssl record header
    if (pSecure->iRecvHead < SSL_MIN_PACKET)
    {
        iResult = SocketRecv(pState->pSock, (char *)pSecure->RecvHead+pSecure->iRecvHead, SSL_MIN_PACKET-pSecure->iRecvHead, 0);
        if (iResult > 0)
        {
            pSecure->iRecvHead += iResult;
            iXfer = 1;
        }
        if (iResult < 0)
        {
            pState->iState = (pState->iState < ST3_SECURE) ? ST_FAIL_SETUP : ST_FAIL_SECURE;
            pState->iClosed = 1;
        }
    }
    // wait for a complete ssl record header before passing this point
    if (pSecure->iRecvHead < SSL_MIN_PACKET)
    {
        return(iXfer);
    }

    /* see if we can determine the full packet size
       this needs to handle the following situations:
       - initial receive of a packet (just the header has been received)
       - receive of the second or later packet in a fragmented handshake packet
       it needs to NOT be executed in the following scenario:
       - when processing a packet that has been received but has not had all of its handshake packets processed */
    if ((pSecure->iRecvProg == pSecure->iRecvSize) && !pSecure->bRecvProc)
    {
        _ProtoSSLUpdateRecvValidate(pState, pSecure);
    }

    // finish receiving ssl record data
    if (pSecure->iRecvProg < pSecure->iRecvSize)
    {
        iResult = SocketRecv(pState->pSock, (char *)pSecure->RecvData+pSecure->iRecvProg, pSecure->iRecvSize-pSecure->iRecvProg, 0);
        if (iResult > 0)
        {
            pSecure->iRecvProg += iResult;
            iXfer = 1;
        }
        if (iResult < 0)
        {
            pState->iState = (pState->iState < ST3_SECURE) ? ST_FAIL_SETUP : ST_FAIL_SECURE;
            pState->iClosed = 1;
        }
    }

    // handle decryption and data validation
    if ((pSecure->iRecvProg == pSecure->iRecvSize) && !pSecure->bRecvProc)
    {
        uint8_t bFirstRecord = (pSecure->iRecvBase == 0) ? TRUE : FALSE;
        int32_t iAlert = 0;
        // at end of receive, process the packet
        if ((iResult = _ProtoSSLRecvPacket(pState, &iAlert)) < 0)
        {
            _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_FATAL, iAlert);
            pState->iState = (pState->iState < ST3_SECURE) ? ST_FAIL_SETUP : ST_FAIL_SECURE;
            _ProtoSSLRecvReset(pSecure);
            pState->iClosed = 1;
        }
        // remember we've received it
        pSecure->bRecvProc = (pSecure->iRecvSize > 0) ? TRUE : FALSE;
        // initial handshake offset needs to be updated to skip stuff decryption might have skipped (e.g. AEAD explicit IV in tls1.2)
        if (bFirstRecord)
        {
            pSecure->iRecvHshkProg = pSecure->iRecvBase;
        }
    }

    // process received packet - note that this might get called multiple times per ssl record, or one or more times per multiple ssl records
    if ((pSecure->iRecvSize > 0) && (pSecure->iRecvProg == pSecure->iRecvSize) && ((pSecure->RecvHead[0] != SSL3_REC_APPLICATION) || (pState->iState == ST3_RECV_HELLO)) && (pState->iClosed == 0))
    {
        _ProtoSSLUpdateRecvPacket(pState, pSecure);
    }

    return(iXfer);
}

/*
    on-demand ca install
*/

/*F********************************************************************************/
/*!
    \Function _ProtoSSLInitiateCARequest

    \Description
        Initiate CA fetch request

    \Input *pState      - module state reference

    \Output
        int32_t         - 0=success, negative=failure

    \Version 02/28/2012 (szhu)
*/
/********************************************************************************F*/
static int32_t _ProtoSSLInitiateCARequest(ProtoSSLRefT *pState)
{
    // we allow only one fetch request for each ssl negotiation attempt
    if (pState->bCertInfoSet && (pState->iCARequestId <= 0) && !pState->bServer)
    {
        if ((pState->iCARequestId = DirtyCertCARequestCert(&pState->CertInfo, pState->strHost, SockaddrInGetPort(&pState->PeerAddr))) > 0)
        {
            // save the failure cert, it will be validated again after fetching CA cert
            if (pState->pCertToVal == NULL)
            {
                pState->pCertToVal = DirtyMemAlloc(sizeof(*pState->pCertToVal), PROTOSSL_MEMID, pState->iMemGroup, pState->pMemGroupUserData);
            }
            // continue to fetch CA cert even if we fail to allocate cert memory
            if (pState->pCertToVal != NULL)
            {
                ds_memcpy(pState->pCertToVal, &pState->pSecure->Cert, sizeof(*pState->pCertToVal));
            }
            return(0);
        }
    }
    return(-1);
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLUpdateCARequest

    \Description
        Update CA fetch request status

    \Input *pState      - module state reference

    \Output
        int32_t         - updated module state

    \Version 02/28/2012 (szhu)
*/
/********************************************************************************F*/
static int32_t _ProtoSSLUpdateCARequest(ProtoSSLRefT *pState)
{
    int32_t iComplete;
    // see if request completed
    if ((iComplete = DirtyCertCARequestDone(pState->iCARequestId)) != 0)
    {
        DirtyCertCARequestFree(pState->iCARequestId);
        pState->iCARequestId = 0;
        // if CA fetch request failed
        if (iComplete < 0)
        {
            _CertificateSetFailureInfo(pState, pState->pCertToVal, TRUE);
            pState->iState = ST_FAIL_CERT_REQUEST;
        }
        // if cert not validated
        else if ((pState->pCertToVal == NULL) || (_ProtoSSLVerifyCertificate(pState, pState->pSecure, pState->pCertToVal, FALSE) != 0))
        {
            _CertificateSetFailureInfo(pState, pState->pCertToVal, TRUE);
            pState->iState = ST_FAIL_CERT_NOTRUST;
        }
        else
        {
            // cert validated
            #if DIRTYCODE_LOGGING
            char strIdentSubject[512], strIdentIssuer[512];
            NetPrintfVerbose((pState->iVerbose, 0, "protossl: cert (%s) validated by ca (%s)\n", _CertificateDebugFormatIdent(&pState->pCertToVal->Subject, strIdentSubject, sizeof(strIdentSubject)),
                _CertificateDebugFormatIdent(&pState->pCertToVal->Issuer, strIdentIssuer, sizeof(strIdentIssuer))));
            #endif
            pState->iState = ST3_RECV_HELLO;
        }

        if (pState->pCertToVal != NULL)
        {
            DirtyMemFree(pState->pCertToVal, PROTOSSL_MEMID, pState->iMemGroup, pState->pMemGroupUserData);
            pState->pCertToVal = NULL;
        }
    }
    return(pState->iState);
}

/*
    module state management
*/

/*F********************************************************************************/
/*!
    \Function _ProtoSSLAllocSecureState

    \Description
        Allocate secure state

    \Input iMemGroup            - memgroup info for alloc
    \Input *pMemGroupUserData   - memgroup info for alloc

    \Output
        SecureStateT *          - secure state, or null on alloc failure

    \Version 03/17/2010 (jbrookes)
*/
/********************************************************************************F*/
static SecureStateT *_ProtoSSLAllocSecureState(int32_t iMemGroup, void *pMemGroupUserData)
{
    SecureStateT *pSecure = DirtyMemAlloc(sizeof(*pSecure), PROTOSSL_MEMID, iMemGroup, pMemGroupUserData);
    if (pSecure != NULL)
    {
        ds_memclr(pSecure, sizeof(*pSecure));
    }
    return(pSecure);
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLResetSecureState

    \Description
        Reset secure state.  Does not affect the TCP connection, if any.

    \Input *pState  - Reference pointer
    \Input iSecure  - secure status (0=disabled, 1=enabled)

    \Output
        int32_t     - SOCKERR_NONE on success, SOCKERR_NOMEM on failure

    \Version 03/17/2010 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _ProtoSSLResetSecureState(ProtoSSLRefT *pState, int32_t iSecure)
{
    SecureStateT *pSecure;

    // acquire access to secure state
    NetCritEnter(&pState->SecureCrit);

    // see if we need to get rid of secure state
    if (!iSecure && (pState->pSecure != NULL))
    {
        DirtyMemFree(pState->pSecure, PROTOSSL_MEMID, pState->iMemGroup, pState->pMemGroupUserData);
        pState->pSecure = NULL;
    }

    // see if we need to alloc secure state
    if (iSecure && (pState->pSecure == NULL))
    {
        pState->pSecure = _ProtoSSLAllocSecureState(pState->iMemGroup, pState->pMemGroupUserData);
    }

    // reset secure state if present
    if ((pSecure = pState->pSecure) != NULL)
    {
        // clear secure state
        ds_memclr(pSecure, sizeof(*pSecure));

        // reset handshake hashes
        _ProtoSSLHandshakeHashInit(pSecure);
    }

    // release access to secure state
    NetCritLeave(&pState->SecureCrit);

    // return allocate error if secure wanted and failed
    return((iSecure && !pSecure) ? SOCKERR_NOMEM : SOCKERR_NONE);
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLResetState

    \Description
        Reset connection back to unconnected state (will disconnect from server).

    \Input *pState  - Reference pointer
    \Input iSecure  - to be completed

    \Output
        int32_t     - SOCKERR_NONE on success, SOCKERR_NOMEM on failure

    \Version 03/25/2004 (gschaefer)
*/
/********************************************************************************F*/
static int32_t _ProtoSSLResetState(ProtoSSLRefT *pState, int32_t iSecure)
{
    // close socket if needed
    if (pState->pSock != NULL)
    {
        pState->iLastSocketError = SocketInfo(pState->pSock, 'serr', 0, NULL, 0);
        SocketClose(pState->pSock);
        pState->pSock = NULL;
    }

    // done with resolver record
    if (pState->pHost != NULL)
    {
        pState->pHost->Free(pState->pHost);
        pState->pHost = NULL;
    }

    // free dirtycert certificate
    if (pState->pCertToVal != NULL)
    {
        DirtyMemFree(pState->pCertToVal, PROTOSSL_MEMID, pState->iMemGroup, pState->pMemGroupUserData);
        pState->pCertToVal = NULL;
    }

    // done with dirtycert
    if (pState->iCARequestId > 0)
    {
        DirtyCertCARequestFree(pState->iCARequestId);
    }
    pState->iCARequestId = 0;

    // reset the state
    pState->iState = ST_IDLE;
    pState->iClosed = 1;
    pState->uAlertLevel = 0;
    pState->uAlertValue = 0;
    pState->bAlertSent = FALSE;

    // reset secure state
    return(_ProtoSSLResetSecureState(pState, iSecure));
}

/*
    trusted store management
*/

/*F********************************************************************************/
/*!
    \Function _ProtoSSLChkCACert

    \Description
        Check to see if the given CA cert already exists in our list of
        CA certs.

    \Input *pNewCACert  - pointer to new CA cert to check for duplicates of

    \Output
        int32_t         - zero=not duplicate, non-zero=duplicate

    \Version 05/11/2011 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _ProtoSSLChkCACert(const X509CertificateT *pNewCACert)
{
    const ProtoSSLCACertT *pCACert;

    for (pCACert = _ProtoSSL_CACerts; pCACert != NULL; pCACert = pCACert->pNext)
    {
        if (!_CertificateCompareIdent(&pCACert->Subject, &pNewCACert->Subject, TRUE) && (pCACert->iKeyModSize == pNewCACert->iKeyModSize) &&
            !memcmp(pCACert->pKeyModData, pNewCACert->KeyModData, pCACert->iKeyModSize))
        {
            break;
        }
    }

    return(pCACert != NULL);
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLAddCACert

    \Description
        Add a new CA certificate to certificate list

    \Input *pCert       - pointer to new cert to add
    \Input bVerified    - TRUE if verified, else false
    \Input iMemGroup    - memgroup to use for alloc
    \Input *pMemGroupUserData - memgroup userdata for alloc

    \Output
        int32_t         - zero=error/duplicate, one=added

    \Version 01/13/2009 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _ProtoSSLAddCACert(X509CertificateT *pCert, uint8_t bVerified, int32_t iMemGroup, void *pMemGroupUserData)
{
    ProtoSSLCACertT *pCACert;
    int32_t iCertSize = sizeof(*pCACert) + pCert->iKeyModSize;

    // see if this certificate already exists
    if (_ProtoSSLChkCACert(pCert))
    {
        _CertificateDebugPrint(pCert, "ignoring redundant add of CA cert");
        return(0);
    }

    // find append point for new CA
    for (pCACert = _ProtoSSL_CACerts; pCACert->pNext != NULL; pCACert = pCACert->pNext)
        ;

    // allocate new record
    if ((pCACert->pNext = (ProtoSSLCACertT *)DirtyMemAlloc(iCertSize, PROTOSSL_MEMID, iMemGroup, pMemGroupUserData)) == NULL)
    {
        _CertificateDebugPrint(pCert, "failed to allocate memory for cert");
        return(0);
    }

    // clear allocated memory
    pCACert = pCACert->pNext;
    ds_memclr(pCACert, iCertSize);

    // if this cert has not already been verified, allocate memory for X509 cert data and copy the X509 cert data for later validation
    if (!bVerified)
    {
        if ((pCACert->pX509Cert = (X509CertificateT *)DirtyMemAlloc(sizeof(*pCert), PROTOSSL_MEMID, iMemGroup, pMemGroupUserData)) == NULL)
        {
            _CertificateDebugPrint(pCert, "failed to allocate memory for X509 cert");
            DirtyMemFree(pCACert->pNext, PROTOSSL_MEMID, iMemGroup, pMemGroupUserData);
            pCACert->pNext = NULL;
            return(0);
        }
        // copy cert data
        ds_memcpy(pCACert->pX509Cert, pCert, sizeof(*pCert));
    }

    // copy textual identity of this certificate
    // (don't need to save issuer since we already trust this certificate)
    ds_memcpy(&pCACert->Subject, &pCert->Subject, sizeof(pCACert->Subject));

    // copy key info
    pCACert->iKeyType = pCert->iKeyType;
    pCACert->iCrvType = pCert->iCrvType;

    // copy exponent data
    pCACert->iKeyExpSize = pCert->iKeyExpSize;
    ds_memcpy(pCACert->KeyExpData, pCert->KeyExpData, pCACert->iKeyExpSize);

    // copy modulus data, immediately following header
    pCACert->iKeyModSize = pCert->iKeyModSize;
    pCACert->pKeyModData = (uint8_t *)pCACert + sizeof(*pCACert);
    ds_memcpy((uint8_t *)pCACert->pKeyModData, pCert->KeyModData, pCACert->iKeyModSize);

    // save memgroup and user info used to allocate
    pCACert->iMemGroup = iMemGroup;

    _CertificateDebugPrint(pCert, "added new certificate authority");
    return(1);
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLSetCACert

    \Description
        Add one or more X.509 CA certificates to the trusted list. A
        certificate added will be available to all ProtoSSL modules for
        the lifetime of the application. This functional can add one or more
        PEM certificates or a single DER certificate.

    \Input *pCertData   - pointer to certificate data (PEM or DER)
    \Input iCertSize    - size of certificate data
    \Input bVerify      - if TRUE verify cert chain on add

    \Output
        int32_t         - negative=error, positive=count of CAs added

    \Notes
        The certificate must be in .DER (binary) or .PEM (base64-encoded)
        format.

    \Version 01/13/2009 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _ProtoSSLSetCACert(const uint8_t *pCertData, int32_t iCertSize, uint8_t bVerify)
{
    int32_t iResult, iCount = -1;
    X509CertificateT Cert;
    uint8_t *pCertBuffer = NULL;
    const int32_t _iMaxCertSize = 4096;
    const uint8_t *pCertBeg, *pCertEnd;
    int32_t iMemGroup;
    uint32_t uCertType;
    void *pMemGroupUserData;
    SecureStateT *pSecure;

    #if DIRTYCODE_LOGGING
    uint32_t uTick = NetTick();
    #endif

    // process PEM signature if present
    if (_CertificateFindData(pCertData, iCertSize, &pCertBeg, &pCertEnd, &uCertType) == 0)
    {
        // no markers -- consume all the data
        pCertBeg = pCertData;
        pCertEnd = pCertData+iCertSize;
    }

    // remember remaining data for possible further parsing
    iCertSize -= pCertEnd-pCertData;
    pCertData = pCertEnd;

    // get memgroup settings for certificate blob
    DirtyMemGroupQuery(&iMemGroup, &pMemGroupUserData);

    // if the cert is base64 encoded we decode it; otherwise we assume it is binary and parse it directly
    if ((iResult = Base64Decode2((int32_t)(pCertEnd-pCertBeg), (const char *)pCertBeg, NULL)) > 0)
    {
        if (iResult > _iMaxCertSize)
        {
            return(-111);
        }
        // allocate cert buffer
        if ((pCertBuffer = (uint8_t *)DirtyMemAlloc(_iMaxCertSize, PROTOSSL_MEMID, iMemGroup, pMemGroupUserData)) == NULL)
        {
            return(-112);
        }
        // decode the cert
        Base64Decode2((int32_t)(pCertEnd-pCertBeg), (const char *)pCertBeg, (char *)pCertBuffer);
        pCertBeg = pCertBuffer;
        pCertEnd = pCertBeg+iResult;
    }

    // allocate temporary secure state to verify certificate with
    if ((pSecure = _ProtoSSLAllocSecureState(iMemGroup, pMemGroupUserData)) == NULL)
    {
        DirtyMemFree(pCertBuffer, PROTOSSL_MEMID, iMemGroup, pMemGroupUserData);
        return(-113);
    }

    // parse the x.509 certificate onto stack
    if ((iResult = _AsnParseCertificate(&Cert, pCertBeg, (int32_t)(pCertEnd-pCertBeg))) == 0)
    {
        // verify signature of this certificate (self-signed allowed)
        if (!bVerify || ((iResult = _ProtoSSLVerifyCertificate(NULL, pSecure, &Cert, TRUE)) == 0))
        {
            // add certificate to CA list
            iCount = _ProtoSSLAddCACert(&Cert, bVerify, iMemGroup, pMemGroupUserData);
        }
    }

    // if CA was PEM encoded and there is extra data, check for more CAs
    while ((iResult == 0) && (iCertSize > 0) && (_CertificateFindData(pCertData, iCertSize, &pCertBeg, &pCertEnd, &uCertType) != 0))
    {
        // remember remaining data for possible further parsing
        iCertSize -=  pCertEnd-pCertData;
        pCertData = pCertEnd;

        // cert must be base64 encoded
        if (((iResult = Base64Decode2((int32_t)(pCertEnd-pCertBeg), (const char *)pCertBeg, NULL)) <= 0) || (iResult > _iMaxCertSize))
        {
            break;
        }
        Base64Decode2((int32_t)(pCertEnd-pCertBeg), (const char *)pCertBeg, (char *)pCertBuffer);

        // parse the x.509 certificate onto stack
        if ((iResult = _AsnParseCertificate(&Cert, pCertBuffer, iResult)) < 0)
        {
            continue;
        }

        // verify signature of this certificate (self-signed allowed)
        if (bVerify && ((iResult = _ProtoSSLVerifyCertificate(NULL, pSecure, &Cert, TRUE)) < 0))
        {
            continue;
        }

        // add certificate to CA list
        iCount += _ProtoSSLAddCACert(&Cert, bVerify, iMemGroup, pMemGroupUserData);
    }

    // cleanup temp secure state
    DirtyMemFree(pSecure, PROTOSSL_MEMID, iMemGroup, pMemGroupUserData);

    // cleanup temp allocation
    if (pCertBuffer != NULL)
    {
        DirtyMemFree(pCertBuffer, PROTOSSL_MEMID, iMemGroup, pMemGroupUserData);
    }

    NetPrintf(("protossl: SSL Perf (CA load) %dms\n", NetTickDiff(NetTick(), uTick)));
    return(iCount);
}

/*
    misc helpers
*/

/*F********************************************************************************/
/*!
    \Function _ProtoSSLSetSockOpt

    \Description
        Set socket options

    \Input  *pState     - module state

    \Version 09/12/2012 (jbrookes) Refactored from ProtoSSLBind() and ProtoSSLConnect()
*/
/********************************************************************************F*/
static void _ProtoSSLSetSockOpt(ProtoSSLRefT *pState)
{
    // set debug level
    SocketControl(pState->pSock, 'spam', pState->iVerbose, NULL, NULL);

    // set recv/send buffer size?
    if (pState->iRecvBufSize != 0)
    {
        SocketControl(pState->pSock, 'rbuf', pState->iRecvBufSize, NULL, NULL);
    }
    if (pState->iSendBufSize != 0)
    {
        SocketControl(pState->pSock, 'sbuf', pState->iSendBufSize, NULL, NULL);
    }

    // set max send/recv rate?
    if (pState->iMaxRecvRate != 0)
    {
        SocketControl(pState->pSock, 'maxr', pState->iMaxRecvRate, NULL, NULL);
    }
    if (pState->iMaxSendRate != 0)
    {
        SocketControl(pState->pSock, 'maxs', pState->iMaxSendRate, NULL, NULL);
    }

    // set keep-alive options?
    if (pState->bKeepAlive != 0)
    {
        SocketControl(pState->pSock, 'keep', pState->bKeepAlive, &pState->uKeepAliveTime, &pState->uKeepAliveTime);
    }

    // set nodelay?
    if (pState->bNoDelay)
    {
        SocketControl(pState->pSock, 'ndly', TRUE, NULL, NULL);
    }

    // set reuseaddr
    if (pState->bReuseAddr)
    {
        SocketControl(pState->pSock, 'radr', TRUE, NULL, NULL);
    }
}

/*F********************************************************************************/
/*!
    \Function _ProtoSSLCacheLocalAddress

    \Description
        Cache value of our local address being used, some connections are very short
        lived making it otherwise difficult to read the local addr info reliably

    \Input *pState      - reference pointer

    \Version 09/13/2017 (cvienneau) used in Qos2.0
*/
/********************************************************************************F*/
static void _ProtoSSLCacheLocalAddress(ProtoSSLRefT *pState)
{
    if (pState->pSock != NULL)
    {
        if (SocketInfo(pState->pSock, 'bind', 0, &pState->LocalAddr, sizeof(pState->LocalAddr)) != 0)
        {
            NetPrintfVerbose((pState->iVerbose, 0, "protossl: _ProtoSSLCacheLocalAddress failed to read local address info from 'bind'.\n"));
        }
    }
    else
    {
        NetPrintfVerbose((pState->iVerbose, 0, "protossl: _ProtoSSLCacheLocalAddress socket is NULL.\n"));
    }
}

/*** Public functions *************************************************************/


/*F********************************************************************************/
/*!
    \Function ProtoSSLStartup

    \Description
        Start up ProtoSSL.  Used to create global state shared across SSL refs.

    \Output
        int32_t         - negative=failure, else success

    \Version 09/14/2012 (jbrookes)
*/
/********************************************************************************F*/
int32_t ProtoSSLStartup(void)
{
    ProtoSSLStateT *pState;
    int32_t iMemGroup;
    void *pMemGroupUserData;

    // make sure we haven't already been called
    if (_ProtoSSL_pState != NULL)
    {
        NetPrintf(("protossl: global state already allocated\n"));
        return(-1);
    }

    // Query current mem group data
    DirtyMemGroupQuery(&iMemGroup, &pMemGroupUserData);

    // allocate and init module state
    if ((pState = DirtyMemAlloc(sizeof(*pState), PROTOSSL_MEMID, iMemGroup, pMemGroupUserData)) == NULL)
    {
        NetPrintf(("protossl: could not allocate global state\n"));
        return(-1);
    }
    ds_memclr(pState, sizeof(*pState));
    pState->iMemGroup = iMemGroup;
    pState->pMemGroupUserData = pMemGroupUserData;
    NetCritInit(&pState->StateCrit, "ProtoSSL Global State");

    // initalize cyptrand module
    if (CryptRandInit() != 0)
    {
        NetCritKill(&pState->StateCrit);
        DirtyMemFree(pState, PROTOSSL_MEMID, iMemGroup, pMemGroupUserData);
        return(-1);
    }

    // set global defaults
    pState->iDfltCiph = PROTOSSL_CIPHER_ALL;
    pState->iDfltVers = SSL3_VERSION;
    pState->iDfltMinVers = SSL3_TLS1_0;
    pState->iDfltCurves = PROTOSSL_CURVE_ALL;

    // save state ref
    _ProtoSSL_pState = pState;
    return(0);
}

/*F********************************************************************************/
/*!
    \Function ProtoSSLShutdown

    \Description
        Shut down ProtoSSL.  Cleans up global state.

    \Version 09/14/2012 (jbrookes)
*/
/********************************************************************************F*/
void ProtoSSLShutdown(void)
{
    ProtoSSLStateT *pState = _ProtoSSL_pState;

    // make sure we haven't already been called
    if (pState == NULL)
    {
        NetPrintf(("protossl: global state not allocated\n"));
        return;
    }

    // deallocate stored CAs
    ProtoSSLClrCACerts();

    // shutdown cyptrand module
    CryptRandShutdown();

    // shut down, deallocate resources
    NetCritKill(&pState->StateCrit);
    DirtyMemFree(pState, PROTOSSL_MEMID, pState->iMemGroup, pState->pMemGroupUserData);

    // clear state pointer
    _ProtoSSL_pState = NULL;
}

/*F********************************************************************************/
/*!
    \Function ProtoSSLCreate

    \Description
        Allocate an SSL connection and prepare for use

    \Output
        ProtoSSLRefT * - module state; NULL=failure

    \Version 03/08/2002 (gschaefer)
*/
/********************************************************************************F*/
ProtoSSLRefT *ProtoSSLCreate(void)
{
    ProtoSSLRefT *pState;
    int32_t iMemGroup;
    void *pMemGroupUserData;

    // Query current mem group data
    DirtyMemGroupQuery(&iMemGroup, &pMemGroupUserData);

    // allocate and init module state
    if ((pState = DirtyMemAlloc(sizeof(*pState), PROTOSSL_MEMID, iMemGroup, pMemGroupUserData)) == NULL)
    {
        NetPrintf(("protossl: could not allocate module state\n"));
        return(NULL);
    }
    ds_memclr(pState, sizeof(*pState));
    pState->iMemGroup = iMemGroup;
    pState->pMemGroupUserData = pMemGroupUserData;

    // set defaults
    pState->iLastSocketError = SOCKERR_NONE;
    pState->iVerbose = SSL_VERBOSE_DEFAULT;
    pState->bSessionResumeEnabled = TRUE;
    pState->iCurveDflt = SSL3_CURVE_DEFAULT;
    pState->uHelloExtn = PROTOSSL_HELLOEXTN_DEFAULT;

    // set defaults with global overrides
    if (_ProtoSSL_pState != NULL)
    {
        pState->uSslVersion = _ProtoSSL_pState->iDfltVers;
        pState->uSslVersionMin = _ProtoSSL_pState->iDfltMinVers;
        pState->uEnabledCiphers = _ProtoSSL_pState->iDfltCiph;
        pState->uEnabledCurves = _ProtoSSL_pState->iDfltCurves;
    }
    else
    {
        pState->uSslVersion = SSL3_VERSION;
        pState->uSslVersionMin = SSL3_TLS1_0;
        pState->uEnabledCiphers = PROTOSSL_CIPHER_ALL;
        pState->uEnabledCurves = PROTOSSL_CURVE_ALL;
    }

    // init secure state critical section
    NetCritInit(&pState->SecureCrit, "ProtoSSL Secure State");

    // return module state
    return(pState);
}

/*F********************************************************************************/
/*!
    \Function ProtoSSLReset

    \Description
        Reset connection back to unconnected state (will disconnect from server).

    \Input *pState      - module state reference

    \Version 03/08/2002 (gschaefer)
*/
/********************************************************************************F*/
void ProtoSSLReset(ProtoSSLRefT *pState)
{
    // reset to unsecure mode
    _ProtoSSLResetState(pState, 0);
}

/*F********************************************************************************/
/*!
    \Function ProtoSSLDestroy

    \Description
        Destroy the module and release its state. Will disconnect from the
        server if required.

    \Input *pState      - module state reference

    \Version 03/08/2002 (gschaefer)
*/
/********************************************************************************F*/
void ProtoSSLDestroy(ProtoSSLRefT *pState)
{
    // reset to unsecure mode (free all secure resources)
    _ProtoSSLResetState(pState, 0);
    // free certificate, if allocated
    if (pState->pCertificate != NULL)
    {
        DirtyMemFree(pState->pCertificate, PROTOSSL_MEMID, pState->iMemGroup, pState->pMemGroupUserData);
    }
    // free private key, if allocated
    if (pState->pPrivateKey != NULL)
    {
        DirtyMemFree(pState->pPrivateKey, PROTOSSL_MEMID, pState->iMemGroup, pState->pMemGroupUserData);
    }
    // kill critical section
    NetCritKill(&pState->SecureCrit);
    // free remaining state
    DirtyMemFree(pState, PROTOSSL_MEMID, pState->iMemGroup, pState->pMemGroupUserData);
}

/*F********************************************************************************/
/*!
    \Function ProtoSSLAccept

    \Description
        Accept an incoming connection.

    \Input *pState      - module state reference
    \Input iSecure      - flag indicating use of secure connection: 1=secure, 0=unsecure
    \Input *pAddr       - where the client's address should be written
    \Input *pAddrlen    - the length of the client's address space

    \Output
        ProtoSSLRefT *  - accepted connection or NULL if not available

    \Version 10/27/2013 (jbrookes)
*/
/********************************************************************************F*/
ProtoSSLRefT *ProtoSSLAccept(ProtoSSLRefT *pState, int32_t iSecure, struct sockaddr *pAddr, int32_t *pAddrlen)
{
    ProtoSSLRefT *pClient;
    SocketT *pSocket;

    // check for connect
    pSocket = SocketAccept(pState->pSock, pAddr, pAddrlen);
    if (pSocket == NULL)
    {
        return(NULL);
    }

    // we have an incoming connection attempt, so create an ssl object for it
    DirtyMemGroupEnter(pState->iMemGroup, pState->pMemGroupUserData);
    pClient = ProtoSSLCreate();
    DirtyMemGroupLeave();
    if (pClient == NULL)
    {
        SocketClose(pSocket);
        return(NULL);
    }

    // set up new ssl object with the socket we just accepted
    if (_ProtoSSLResetState(pClient, iSecure) != SOCKERR_NONE)
    {
        ProtoSSLDestroy(pClient);
        return(NULL);
    }
    pClient->pSock = pSocket;
    ds_memcpy(&pClient->PeerAddr, pAddr, *pAddrlen);

    // update socket status
    SocketInfo(pClient->pSock, 'stat', 0, NULL, 0);

    // set client state
    pClient->iState = (pClient->pSecure ? ST3_RECV_HELLO : ST_UNSECURE);
    pClient->iClosed = 0;
    pClient->bServer = TRUE;
    return(pClient);
}

/*F********************************************************************************/
/*!
    \Function ProtoSSLBind

    \Description
        Create a socket bound to the given address.

    \Input *pState  - module state reference
    \Input *pAddr   - the IPv4 address
    \Input iAddrlen - the size of the IPv4 address.

    \Output
        int32_t     - SOCKERR_xxx (zero=success, negative=failure)

    \Version 03/03/2004 (sbevan)
*/
/********************************************************************************F*/
int32_t ProtoSSLBind(ProtoSSLRefT *pState, const struct sockaddr *pAddr, int32_t iAddrlen)
{
    // if we had a socket, get last error from it and close it
    if (pState->pSock != NULL)
    {
        pState->iLastSocketError = SocketInfo(pState->pSock, 'serr', 0, NULL, 0);
        SocketClose(pState->pSock);
    }

    // create the socket
    if ((pState->pSock = SocketOpen(AF_INET, SOCK_STREAM, 0)) == NULL)
    {
        return(SOCKERR_OTHER);
    }

    // set socket options
    _ProtoSSLSetSockOpt(pState);

    // do the bind, return result
    return(SocketBind(pState->pSock, pAddr, iAddrlen));
}

/*F********************************************************************************/
/*!
    \Function ProtoSSLConnect

    \Description
        Make a secure connection to an SSL server.

    \Input *pState  - module state reference
    \Input iSecure  - flag indicating use of secure connection (1=secure, 0=unsecure)
    \Input *pAddr   - textual form of address (1.2.3.4 or www.ea.com)
    \Input uAddr    - the IP address of the server (if not in textual form)
    \Input iPort    - the TCP port of the server (if not in textual form)

    \Output
        int32_t     - SOCKERR_xxx (zero=success, negative=failure)

    \Version 03/08/2002 (gschaefer)
*/
/********************************************************************************F*/
int32_t ProtoSSLConnect(ProtoSSLRefT *pState, int32_t iSecure, const char *pAddr, uint32_t uAddr, int32_t iPort)
{
    int32_t iIndex;
    int32_t iError;

    // reset connection state
    iError = _ProtoSSLResetState(pState, iSecure);
    if (iError != SOCKERR_NONE)
    {
        return(iError);
    }

    // only allow secure connection if dirtycert service name has been set
    if ((iSecure != 0) && (DirtyCertStatus('snam', NULL, 0) < 0))
    {
        NetPrintf(("protossl: ************************************************************************************\n"));
        NetPrintf(("protossl: ProtoSSLConnect() requires a valid DirtyCert service name in the format\n"));
        NetPrintf(("protossl: \"game-year-platform\", set when calling NetConnStartup() by using the -servicename\n"));
        NetPrintf(("protossl: argument, to be set before SSL use is allowed.  If a service name doesn't include\n"));
        NetPrintf(("protossl: dashes it is assumed to simply be the 'game' identifier, in which case DirtySDK will\n"));
        NetPrintf(("protossl: fill in the year and platform.  For titles using BlazeSDK, the service name specified\n"));
        NetPrintf(("protossl: must match the BlazeSDK service name, therefore the full name should be specified.\n"));
        NetPrintf(("protossl: *** PLEASE SET A UNIQUE AND MEANINGFUL SERVICE NAME, EVEN FOR TOOL OR SAMPLE USE ***\n"));
        return(SOCKERR_INVALID);
    }

    // allocate the socket
    if ((pState->pSock = SocketOpen(AF_INET, SOCK_STREAM, 0)) == NULL)
    {
        return(SOCKERR_NORSRC);
    }

    // set socket options
    _ProtoSSLSetSockOpt(pState);

    // init peer structure
    SockaddrInit(&pState->PeerAddr, AF_INET);

    // clear previous cert info, if any
    pState->bCertInfoSet = FALSE;
    ds_memclr(&pState->CertInfo, sizeof(pState->CertInfo));

    // handle default address case
    if (pAddr == NULL)
    {
        pAddr = "";
    }

    // parse the address string
    for (iIndex = 0; (pAddr[iIndex] != 0) && (pAddr[iIndex] != ':') && ((unsigned)iIndex < sizeof(pState->strHost)-1); ++iIndex)
    {
        // copy over to host
        pState->strHost[iIndex] = pAddr[iIndex];
    }
    pState->strHost[iIndex] = 0;

    // attempt to set host address
    SockaddrInSetAddrText(&pState->PeerAddr, pState->strHost);
    if (SockaddrInGetAddr(&pState->PeerAddr) == 0)
    {
        SockaddrInSetAddr(&pState->PeerAddr, uAddr);
    }

    // attempt to set peer address
    if (pAddr[iIndex] == ':')
    {
        SockaddrInSetPort(&pState->PeerAddr, atoi(pAddr+iIndex+1));
    }
    else
    {
        SockaddrInSetPort(&pState->PeerAddr, iPort);
    }

    // see if we need to start DNS request
    if (SockaddrInGetAddr(&pState->PeerAddr) == 0)
    {
        // do dns lookup prior to connect
        pState->pHost = SocketLookup(pState->strHost, 30*1000);
        pState->iState = ST_ADDR;
    }
    else
    {
        // set to connect state
        pState->iState = ST_CONN;
    }

    // return error code
    return(SOCKERR_NONE);
}

/*F********************************************************************************/
/*!
    \Function ProtoSSLListen

    \Description
        Start listening for an incoming connection.

    \Input *pState  - module state reference
    \Input iBacklog - number of pending connections allowed

    \Output
        int32_t     - SOCKERR_xxx (zero=success, negative=failure)

    \Version 03/03/2004 (sbevan)
*/
/********************************************************************************F*/
int32_t ProtoSSLListen(ProtoSSLRefT *pState, int32_t iBacklog)
{
    return(SocketListen(pState->pSock, iBacklog));
}

/*F********************************************************************************/
/*!
    \Function ProtoSSLDisconnect

    \Description
        Disconnect from the server

    \Input *pState  - module state reference

    \Output
        int32_t     - SOCKERR_xxx (zero=success, negative=failure)

    \Version 03/08/2002 (gschaefer)
*/
/********************************************************************************F*/
int32_t ProtoSSLDisconnect(ProtoSSLRefT *pState)
{
    if (pState->pSock)
    {
        // send a close_notify alert as per http://tools.ietf.org/html/rfc5246#section-7.2.1
        if ((pState->pSecure != NULL) && (pState->iState == ST3_SECURE))
        {
            // send the alert
            _ProtoSSLSendAlert(pState, SSL3_ALERT_LEVEL_WARNING, SSL3_ALERT_DESC_CLOSE_NOTIFY);
        }

        /* if in server mode, just close the write side.  on the client we do an immediate hard
           close of the socket as the calling application that is polling to drive our operation
           may stop updating us once they have received all of the data, which would prevent us
           from closing the socket */
        if (pState->bServer)
        {
            SocketShutdown(pState->pSock, SOCK_NOSEND);
        }
        else
        {
            SocketClose(pState->pSock);
            pState->pSock = NULL;
        }
    }

    pState->iState = ST_IDLE;
    pState->iClosed = 1;

    // done with dirtycert
    if (pState->iCARequestId > 0)
    {
        DirtyCertCARequestFree(pState->iCARequestId);
    }
    pState->iCARequestId = 0;
    return(SOCKERR_NONE);
}

/*F********************************************************************************/
/*!
    \Function ProtoSSLUpdate

    \Description
        Give time to module to do its thing (should be called
        periodically to allow module to perform work). Calling
        once per 100ms or so should be enough.

        This is actually a collection of state functions plus
        the overall update function. They are kept together for
        ease of reading.

    \Input *pState - module state reference

    \Version 11/10/2005 gschaefer
*/
/********************************************************************************F*/
void ProtoSSLUpdate(ProtoSSLRefT *pState)
{
    int32_t iXfer;
    int32_t iResult;
    SecureStateT *pSecure = pState->pSecure;

    // resolve the address
    if (pState->iState == ST_ADDR)
    {
        // check for completion
        if (pState->pHost->Done(pState->pHost))
        {
            pState->iState = (pState->pHost->addr != 0) ? ST_CONN : ST_FAIL_DNS;
            SockaddrInSetAddr(&pState->PeerAddr, pState->pHost->addr);
            // free the record
            pState->pHost->Free(pState->pHost);
            pState->pHost = NULL;
        }
    }

    // see if we should start a connection
    if (pState->iState == ST_CONN)
    {
        // start the connection attempt
        if ((iResult = SocketConnect(pState->pSock, &pState->PeerAddr, sizeof pState->PeerAddr)) == SOCKERR_NONE)
        {
            pState->iState = ST_WAIT_CONN;
        }
        else
        {
            pState->iState = ST_FAIL_CONN;
            pState->iClosed = 1;
        }
    }

    // wait for connection
    if (pState->iState == ST_WAIT_CONN)
    {
        iResult = SocketInfo(pState->pSock, 'stat', 0, NULL, 0);
        if (iResult > 0)
        {
            pState->iState = pSecure ? ST3_SEND_HELLO : ST_UNSECURE;
            pState->iClosed = 0;
            _ProtoSSLCacheLocalAddress(pState);
        }
        if (iResult < 0)
        {
            pState->iState = ST_FAIL_CONN;
            pState->iClosed = 1;
        }
    }

    // handle secure i/o (non-secure is done immediately in ProtoSSLSend/ProtoSSLRecv)
    while ((pState->pSock != NULL) && (pState->iState >= ST3_SEND_HELLO) && (pState->iState <= ST3_SECURE))
    {
        // get access to secure state
        NetCritEnter(&pState->SecureCrit);

        // update async processing, if any
        if (_ProtoSSLUpdateAsync(pState, pSecure))
        {
            NetCritLeave(&pState->SecureCrit);
            break;
        }

        // update send processing
        iXfer = _ProtoSSLUpdateSend(pState, pSecure);

        // update recv processing
        iXfer += _ProtoSSLUpdateRecv(pState, pSecure);

        // release access to secure state
        NetCritLeave(&pState->SecureCrit);

        // break out of loop if no i/o activity
        if (iXfer == 0)
        {
            break;
        }
    }

    // wait for CA cert (this comes last intentionally)
    if (pState->iState == ST_WAIT_CA)
    {
        // acquire secure state crit section to guard dirtycert access
        NetCritEnter(&pState->SecureCrit);

        // update CA request processing
        _ProtoSSLUpdateCARequest(pState);

        // release secure state crit section
        NetCritLeave(&pState->SecureCrit);
    }
}

/*F********************************************************************************/
/*!
    \Function ProtoSSLSend

    \Description
        Send data to the server over secure TCP connection (actually, whether the
        connection is secure or not is determined by the secure flag passed during
        the SSLConnect call).

    \Input *pState  - module state reference
    \Input *pBuffer - data to send
    \Input iLength  - length of data (if negative, input data is assumed to be null-terminated string)

    \Output
        int32_t     - negative=error, otherwise number of bytes sent

    \Version 06/03/2002 (gschaefer)
*/
/********************************************************************************F*/
int32_t ProtoSSLSend(ProtoSSLRefT *pState, const char *pBuffer, int32_t iLength)
{
    int32_t iResult = SOCKERR_CLOSED;
    SecureStateT *pSecure = pState->pSecure;

    // allow easy string sends
    if (iLength < 0)
    {
        iLength = (int32_t)strlen(pBuffer);
    }
    // guard against zero-length sends, which can result in an invalid send condition with some stream (e.g. RC4) ciphers
    if (iLength == 0)
    {
        return(0);
    }

    // make sure connection established
    if (pState->iState == ST3_SECURE)
    {
        iResult = 0;

        // get access to secure state
        NetCritEnter(&pState->SecureCrit);

        // make sure buffer is empty
        if (pSecure->iSendSize == 0)
        {
            // limit send length
            if (iLength > SSL_SNDLIM_PACKET)
            {
                iLength = SSL_SNDLIM_PACKET;
            }

            // setup packet for send
            if (_ProtoSSLSendPacket(pState, SSL3_REC_APPLICATION, NULL, 0, pBuffer, iLength) == 0)
            {
                iResult = iLength;
                // try and send now
                ProtoSSLUpdate(pState);
            }
        }

        // release access to secure state
        NetCritLeave(&pState->SecureCrit);
    }

    // handle unsecure sends
    if (pState->iState == ST_UNSECURE)
    {
        iResult = SocketSend(pState->pSock, pBuffer, iLength, 0);
    }

    // return the result
    return(iResult);
}

/*F********************************************************************************/
/*!
    \Function ProtoSSLRecv

    \Description
        Receive data from the server

    \Input *pState  - module state reference
    \Input *pBuffer - receiver data
    \Input iLength  - maximum buffer length

    \Output
        int32_t     - negative=error, zero=nothing available, positive=bytes received

    \Version 03/08/2002 (gschaefer)
*/
/********************************************************************************F*/
int32_t ProtoSSLRecv(ProtoSSLRefT *pState, char *pBuffer, int32_t iLength)
{
    SecureStateT *pSecure = pState->pSecure;
    int32_t iResult = 0;

    // make sure in right state
    if (pState->iState == ST3_SECURE)
    {
        // get access to secure state
        NetCritEnter(&pState->SecureCrit);

        // check for more data if no packet pending
        if ((pSecure->iRecvProg == 0) || (pSecure->iRecvProg != pSecure->iRecvSize))
        {
            ProtoSSLUpdate(pState);
        }

        // check for end of data
        if (((pSecure->iRecvSize < SSL_MIN_PACKET) || (pSecure->iRecvProg < pSecure->iRecvSize)) && (pState->iClosed))
        {
            iResult = SOCKERR_CLOSED;
        }
        // see if data pending
        else if ((pSecure->iRecvProg == pSecure->iRecvSize) && (pSecure->iRecvBase < pSecure->iRecvSize) &&
            (pSecure->RecvHead[0] == SSL3_REC_APPLICATION))
        {
            iResult = pSecure->iRecvSize-pSecure->iRecvBase;
            // only return what user can store
            if (iResult > iLength)
            {
                iResult = iLength;
            }
            // return the data
            ds_memcpy(pBuffer, pSecure->RecvData+pSecure->iRecvBase, iResult);
            pSecure->iRecvBase += iResult;

            // see if we can grab a new packet
            if (pSecure->iRecvBase >= pSecure->iRecvSize)
            {
                _ProtoSSLRecvReset(pSecure);
            }
        }

        // release access to secure state
        NetCritLeave(&pState->SecureCrit);
    }

    // handle unsecure receive
    if (pState->iState == ST_UNSECURE)
    {
        iResult = SocketRecv(pState->pSock, pBuffer, iLength, 0);
    }

    // return error if in failure state
    if (pState->iState >= ST_FAIL)
    {
        iResult = -1;
    }

    // terminate buffer if there is room
    if ((iResult > 0) && (iResult < iLength))
    {
        pBuffer[iResult] = 0;
    }

    // return the data size
    return(iResult);
}

/*F********************************************************************************/
/*!
    \Function ProtoSSLStat

    \Description
        Return the current module status (according to selector)

    \Input *pState  - module state reference
    \Input iSelect  - status selector ('conn'=return "am i connected" flag)
    \Input pBuffer  - buffer pointer
    \Input iLength  - buffer size

    \Output
        int32_t     - negative=error, zero=false, positive=true

    \Notes
        Selectors are:

        \verbatim
            SELECTOR    DESCRIPTION
            'addr'      Address of peer we are connecting/connected to
            'alpn'      Get the negotiated protocol using the ALPN extension from the secure state
            'alrt'      Return alert status 0=no alert, 1=recv alert, 2=sent alert; alert desc copied to pBuffer
            'cert'      Return SSL cert info (valid after 'fail')
            'cfip'      TRUE/FALSE indication if a CA fetch is in progress
            'ciph'      Cipher suite negotiated (string name in output buffer)
            'crpt'      Returns whether we are performing any crypto operations at this time
            'fail'      Return PROTOSSL_ERROR_* or zero if no error
            'hres'      Return hResult containing either the socket error or ssl error
            'htim'      Return timing of most recent SSL handshake in milliseconds
            'ladd'      cached local client address used for the last connection; copy into pBuffer, buffer must be at least sizeof(struct sockaddr)
            'maxr'      Return max recv rate (0=uncapped)
            'maxs'      Return max send rate (0=uncapped)
            'recv'      Return number of bytes in recv buffer (secure only)
            'resu'      Returns whether session was resumed or not
            'rsao'      [DEPRECATED] - same as 'crpt'
            'send'      Return number of bytes in send buffer (secure only)
            'serr'      Return socket error
            'salg'      Signature algorithm negotiated (string name in output buffer); not available on resume
            'sock'      Copy SocketT ref to output buffer
            'stat'      Return like SocketInfo('stat')
            'vers'      Return current SSL version
        \endverbatim

    \Version 03/08/2002 (gschaefer)
*/
/********************************************************************************F*/
int32_t ProtoSSLStat(ProtoSSLRefT *pState, int32_t iSelect, void *pBuffer, int32_t iLength)
{
    int32_t iResult = -1;

    // pass-through to SocketInfo(NULL,...)
    if (pState == NULL)
    {
        return(SocketInfo(NULL, iSelect, 0, pBuffer, iLength));
    }

    // return address of peer we are trying to connect to
    if (iSelect == 'addr')
    {
        if ((pBuffer != NULL) && (iLength == sizeof(pState->PeerAddr)))
        {
            ds_memcpy(pBuffer, &pState->PeerAddr, iLength);
        }
        return(SockaddrInGetAddr(&pState->PeerAddr));
    }
    if ((iSelect == 'alpn') && (pState->pSecure != NULL))
    {
        if (pBuffer != NULL)
        {
            ds_strnzcpy(pBuffer, pState->pSecure->strAlpnProtocol, iLength);
        }

        return(0);
    }
    // return most recent alert if any
    if (iSelect == 'alrt')
    {
        if ((pBuffer != NULL) && (iLength == sizeof(ProtoSSLAlertDescT)))
        {
            ProtoSSLAlertDescT Alert;
            if ((iResult = _ProtoSSLGetAlert(pState, &Alert, pState->uAlertLevel, pState->uAlertValue)) != 0)
            {
                ds_memcpy(pBuffer, &Alert, sizeof(Alert));
                iResult = pState->bAlertSent ? 2 : 1;
            }
        }
        return(iResult);
    }
    // return certificate info (valid after 'fail' response)
    if ((iSelect == 'cert') && (pBuffer != NULL) && (iLength == sizeof(pState->CertInfo)))
    {
        ds_memcpy(pBuffer, &pState->CertInfo, sizeof(pState->CertInfo));
        return(0);
    }
    // return if a CA fetch request is in progress
    if (iSelect == 'cfip')
    {
        return((pState->iState == ST_WAIT_CA) ? 1 : 0);
    }
    // return current cipher suite
    if ((iSelect == 'ciph') && (pState->pSecure != NULL) && (pState->pSecure->pCipher != NULL))
    {
        if (pBuffer != NULL)
        {
            ds_strnzcpy(pBuffer, pState->pSecure->pCipher->strName, iLength);
        }
        return(pState->pSecure->pCipher->uId);
    }
    // return whether we are performing any crypto operations at this time
    if ((iSelect == 'crpt') || (iSelect == 'rsao'))
    {
        switch (pState->iState)
        {
            case ST3_SEND_HELLO:
            case ST3_SEND_EXTN:
            case ST3_SEND_KEY:
            case ST3_SEND_VERIFY:
            case ST3_SEND_CHANGE:
            case ST3_RECV_CHANGE:
            case ST3_PROC_ASYNC:
                iResult = TRUE;
                break;
            default:
                iResult = FALSE;
                break;
        }
        return(iResult);
    }
    // return timing of most recent SSL handshake in milliseconds
    if ((iSelect == 'htim') && (pState->pSecure != NULL))
    {
        return(pState->pSecure->uTimer);
    }
    // cached local client address used for the last connection; copy into pBuffer, buffer must be at least sizeof(struct sockaddr)
    if (iSelect == 'ladd')
    {
        if ((pBuffer != NULL) && (iLength >= (int32_t)sizeof(pState->LocalAddr)))
        {
            ds_memcpy(pBuffer, &pState->LocalAddr, sizeof(pState->LocalAddr));
            return(0);
        }
        return(-1);
    }
    // return configured max receive rate
    if (iSelect == 'maxr')
    {
        return(pState->iMaxRecvRate);
    }
    // return configured max send rate
    if (iSelect == 'maxs')
    {
        return(pState->iMaxSendRate);
    }
    // return number of bytes in recv buffer (useful only when connection type is secure)
    if (iSelect == 'recv')
    {
        return((pState->pSecure != NULL) ? pState->pSecure->iRecvSize-pState->pSecure->iRecvProg : 0);
    }
    // return whether session was resumed or not
    if ((iSelect == 'resu') && (pState->pSecure != NULL))
    {
        return(pState->pSecure->bSessionResume);
    }
    // return current signature algorithm if available
    if ((iSelect == 'salg') && (pState->pSecure != NULL))
    {
        if ((pBuffer != NULL) && (pState->pSecure->Cert.iSigType >= ASN_OBJ_RSA_PKCS_MD5))
        {
            ds_strnzcpy(pBuffer, _SSL3_strSignatureTypes[pState->pSecure->Cert.iSigType-ASN_OBJ_RSA_PKCS_MD5], iLength);
            return(1);
        }
    }
    // return number of bytes in send buffer (useful only when connection type is secure)
    if (iSelect == 'send')
    {
        return((pState->pSecure != NULL) ? pState->pSecure->iSendSize-pState->pSecure->iSendProg : 0);
    }
    // return last socket error
    if (iSelect == 'serr')
    {
        // pass through to socket module if we have a socket, else return cached last error
        return((pState->pSock != NULL) ? SocketInfo(pState->pSock, iSelect, 0, pBuffer, iLength) : pState->iLastSocketError);
    }
    // return socket ref
    if (iSelect == 'sock')
    {
        if ((pBuffer == NULL) || (iLength != sizeof(pState->pSock)))
        {
            return(-1);
        }
        ds_memcpy(pBuffer, &pState->pSock, sizeof(pState->pSock));
        return(0);
    }
    // return current ssl version for the connection
    if ((iSelect == 'vers') && (pState->pSecure != NULL))
    {
        if (pBuffer != NULL)
        {
            ds_strnzcpy(pBuffer, _SSL3_strVersionNames[pState->pSecure->uSslVersion&0xff], iLength);
        }
        return(pState->pSecure->uSslVersion);
    }
    // return failure code
    if (iSelect == 'fail')
    {
        if (pState->iState & ST_FAIL)
        {
            switch (pState->iState)
            {
                case ST_FAIL_DNS:
                    iResult = PROTOSSL_ERROR_DNS;
                    break;
                case ST_FAIL_CONN:
                    iResult = PROTOSSL_ERROR_CONN;
                    break;
                case ST_FAIL_CONN_SSL2:
                    iResult = PROTOSSL_ERROR_CONN_SSL2;
                    break;
                case ST_FAIL_CONN_NOTSSL:
                    iResult = PROTOSSL_ERROR_CONN_NOTSSL;
                    break;
                case ST_FAIL_CONN_MINVERS:
                    iResult = PROTOSSL_ERROR_CONN_MINVERS;
                    break;
                case ST_FAIL_CONN_MAXVERS:
                    iResult = PROTOSSL_ERROR_CONN_MAXVERS;
                    break;
                case ST_FAIL_CONN_NOCIPHER:
                    iResult = PROTOSSL_ERROR_CONN_NOCIPHER;
                    break;
                case ST_FAIL_CONN_NOCURVE:
                    iResult = PROTOSSL_ERROR_CONN_NOCURVE;
                    break;    
                case ST_FAIL_CERT_NONE:
                    iResult = PROTOSSL_ERROR_CERT_MISSING;
                    break;
                case ST_FAIL_CERT_INVALID:
                    iResult = PROTOSSL_ERROR_CERT_INVALID;
                    break;
                case ST_FAIL_CERT_HOST:
                    iResult = PROTOSSL_ERROR_CERT_HOST;
                    break;
                case ST_FAIL_CERT_NOTRUST:
                    iResult = PROTOSSL_ERROR_CERT_NOTRUST;
                    break;
                case ST_FAIL_CERT_BADDATE:
                    iResult = PROTOSSL_ERROR_CERT_BADDATE;
                    break;
                case ST_FAIL_CERT_REQUEST:
                    iResult = PROTOSSL_ERROR_CERT_REQUEST;
                    break;
                case ST_FAIL_SETUP:
                    iResult = PROTOSSL_ERROR_SETUP;
                    break;
                case ST_FAIL_SECURE:
                    iResult = PROTOSSL_ERROR_SECURE;
                    break;
                default:
                    iResult = PROTOSSL_ERROR_UNKNOWN;
                    break;
            }
        }
        else
        {
            iResult = 0;
        }
        return(iResult);
    }

    if (iSelect == 'hres')
    {
        uint32_t hResult;
        int32_t iSerr = ProtoSSLStat(pState, 'serr', NULL, 0);
        int32_t iFail = ProtoSSLStat(pState, 'fail', NULL, 0);

        if (iSerr < SOCKERR_CLOSED)
        {
            hResult = DirtyErrGetHResult(DIRTYAPI_SOCKET, iSerr, TRUE);
        }
        else if (iFail != 0)
        {
            hResult = DirtyErrGetHResult(DIRTYAPI_PROTO_SSL, iFail, TRUE);
        }
        else
        {
            hResult = DirtyErrGetHResult(DIRTYAPI_PROTO_SSL, 0, FALSE); //success hResult
        }
        return(hResult);
    }

    // only pass through if socket is valid
    if (pState->pSock != NULL)
    {
        // special processing for 'stat' selector
        if (iSelect == 'stat')
        {
            // if we're in a failure state, return error
            if (pState->iState >= ST_FAIL)
            {
               return(-1);
            }
            // don't check connected status until we are connected and done with secure negotiation (if secure)
            if (pState->iState < ST3_SECURE)
            {
               return(0);
            }
            // if we're connected (in ST_UNSECURE or ST3_SECURE state) fall through
        }

        // pass through request to the socket module
        iResult = SocketInfo(pState->pSock, iSelect, 0, pBuffer, iLength);
    }
    return(iResult);
}

/*F********************************************************************************/
/*!
    \Function ProtoSSLControl

    \Description
        ProtoSSL control function.  Different selectors control different behaviors.

    \Input *pState  - module state reference
    \Input iSelect  - control selector
    \Input iValue   - selector specific
    \Input iValue2  - selector specific
    \Input *pValue  - selector specific

    \Output
        int32_t     - selector specific

    \Notes
        Selectors are:

        \verbatim
            SELECTOR    DESCRIPTION
            'alpn'      Set the ALPN protocols (using IANA registerd named) as a comma delimited list
            'ccrt'      Set client certificate level (0=disabled, 1=requested, 2=required)
            'ciph'      Set enabled/disabled ciphers (PROTOSSL_CIPHER_*)
            'crvd'      Set default curve (-1=no default; else [0,SSL3_EC_MAX]
            'curv'      Set enabled/disabled curves (PROTOSS_CURVE_*)
            'extn'      Set enabled ClientHello extensions (PROTOSSL_HELLOEXTN_*), (default=0=disabled)
            'gcph'      Set global cipher default
            'gcrv'      Set global curve default
            'gvrs'      Set global version default
            'gvmn'      Set global version min default
            'host'      Set remote host
            'hreq'      Send HelloRequest (server only)
            'maxr'      Set max recv rate (0=uncapped, default)
            'maxs'      Set max send rate (0=uncapped, default)
            'ncrt'      Disable client certificate validation
            'rbuf'      Set socket recv buffer size (must be called before Connect())
            'resu'      Set whether session resume is enabled or disabled (default=1=enabled)
            'sbuf'      Set socket send buffer size (must be called before Connect())
            'scrt'      Set certificate (pValue=cert, iValue=len)
            'snod'      Set whether TCP_NODELAY option is enabled or disabled on the socket (must be called before Connect())
            'secu'      Start secure negotiation on an established unsecure connection
            'skey'      Set private key (pValue=key, iValue=len)
            'skep'      Set socket keep-alive settings (iValue=enable/disable, iValue2=keep-alive time/interval)
            'spam'      Set debug logging level (default=1)
            'vers'      Set client-requested SSL version (default=0x302, TLS1.1)
            'vmin'      Set minimum SSL version application will accept
        \endverbatim

    \Version 01/27/2009 (jbrookes)
*/
/********************************************************************************F*/
int32_t ProtoSSLControl(ProtoSSLRefT *pState, int32_t iSelect, int32_t iValue, int32_t iValue2, void *pValue)
{
    int32_t iResult = -1;

    if (iSelect == 'alpn')
    {
        uint16_t uProtocol, uAlpnExtensionLength;
        const char *pSrc = (const char *)pValue;
        if (pSrc == NULL)
        {
            NetPrintf(("protossl: invalid ALPN extension protocol list provided\n"));
            return(-1);
        }
        NetPrintfVerbose((pState->iVerbose, 0, "protossl: setting the ALPN extension protocols using %s\n", (const char *)pValue));
        ds_memclr(pState->aAlpnProtocols, sizeof(pState->aAlpnProtocols));

        for (uProtocol = 0, uAlpnExtensionLength = 0; uProtocol < SSL_ALPN_MAX_PROTOCOLS; uProtocol += 1)
        {
            AlpnProtocolT *pProtocol = &pState->aAlpnProtocols[uProtocol];
            if ((pProtocol->uLength = (uint8_t)ds_strsplit(pSrc, ',', pProtocol->strName, sizeof(pProtocol->strName), &pSrc)) == 0)
            {
                break;
            }

            uAlpnExtensionLength += pProtocol->uLength;
            uAlpnExtensionLength += sizeof(pProtocol->uLength);
        }
        pState->uNumAlpnProtocols = uProtocol;
        pState->uAlpnExtensionLength = uAlpnExtensionLength;

        return(0);
    }
    if (iSelect == 'ccrt')
    {
        NetPrintf(("protossl: setting client cert level to %d\n", iValue));
        pState->iClientCertLevel = iValue;
        return(0);
    }
    if (iSelect == 'ciph')
    {
        pState->uEnabledCiphers = (uint32_t)iValue;
        NetPrintfVerbose((pState->iVerbose, 0, "protossl: enabled ciphers=%d\n", iValue));
        return(0);
    }
    if (iSelect == 'crvd')
    {
        // attempt to validate the curve is enabled
        int8_t iCurveDflt = (int8_t)DS_CLAMP(iValue, -1, SSL3_CURVE_MAX);
        if ((iCurveDflt == -1) || ((_SSL3_EllipticCurves[iCurveDflt].uId & pState->uEnabledCurves) != 0))
        {
            pState->iCurveDflt = iCurveDflt;
            NetPrintf(("protossl: choosing curve %d\n", pState->iCurveDflt));
            return(0);
        }
        else
        {
            NetPrintf(("protossl: selected curve %d is disabled and cannot be used as default\n", iCurveDflt));
            return(-1);
        }
    }
    if (iSelect == 'curv')
    {
        pState->uEnabledCurves = (uint32_t)iValue;
        NetPrintfVerbose((pState->iVerbose, 0, "protossl: enabled curves=%u\n", pState->uEnabledCurves));
        return(0);
    }
    if (iSelect == 'extn')
    {
        pState->uHelloExtn = (uint8_t)iValue;
        NetPrintfVerbose((pState->iVerbose, 0, "protossl: clienthello extensions set to 0x%02x\n", pState->uHelloExtn));
        return(0);
    }
    // handle global settings
    if (((iSelect == 'gcph') || (iSelect == 'gcrv') || (iSelect == 'gver') || (iSelect == 'gvmn')) && (_ProtoSSL_pState != NULL))
    {
        if (iSelect == 'gcph')
        {
            NetPrintf(("protossl: setting global default cipher mask to 0x%x\n", iValue));
            _ProtoSSL_pState->iDfltCiph = iValue;
            return(0);
        }
        if (iSelect == 'gcrv')
        {
            NetPrintf(("protossl: setting global default curve mask to 0x%08x\n", iValue));
            _ProtoSSL_pState->iDfltCurves = iValue;
            return(0);
        }
        if (iSelect == 'gver')
        {
            NetPrintf(("protossl: setting global default version to 0x%x\n", iValue));
            _ProtoSSL_pState->iDfltVers = iValue;
            return(0);
        }
        if (iSelect == 'gvmn')
        {
            NetPrintf(("protossl: setting global default min version to 0x%x\n", iValue));
            _ProtoSSL_pState->iDfltMinVers = iValue;
            return(0);
        }
    }
    if (iSelect == 'host')
    {
        NetPrintf(("protossl: setting host to '%s'\n", (const char *)pValue));
        ds_strnzcpy(pState->strHost, (const char *)pValue, sizeof(pState->strHost));
        return(0);
    }
    if ((iSelect == 'hreq') && (pState->bServer) && (pState->iState == ST3_SECURE))
    {
        NetPrintf(("protossl: sending Hello Request\n"));
        pState->iState = ST3_SEND_HELLO_REQUEST;
        return(0);
    }
    if (iSelect == 'maxr')
    {
        pState->iMaxRecvRate = iValue;
        if (pState->pSock != NULL)
        {
            SocketControl(pState->pSock, iSelect, iValue, NULL, NULL);
        }
        return(0);
    }
    if (iSelect == 'maxs')
    {
        pState->iMaxSendRate = iValue;
        if (pState->pSock != NULL)
        {
            SocketControl(pState->pSock, iSelect, iValue, NULL, NULL);
        }
        return(0);
    }
    if (iSelect == 'ncrt')
    {
        pState->bAllowAnyCert = (uint8_t)iValue;
        return(0);
    }
    if (iSelect == 'radr')
    {
        pState->bReuseAddr = TRUE;
        return(0);
    }
    if (iSelect == 'rbuf')
    {
        pState->iRecvBufSize = iValue;
        return(0);
    }
    if (iSelect == 'resu')
    {
        pState->bSessionResumeEnabled = iValue ? TRUE : FALSE;
        return(0);
    }
    if (iSelect == 'sbuf')
    {
        pState->iSendBufSize = iValue;
        return(0);
    }
    if (iSelect == 'scrt')
    {
        if (pState->pCertificate != NULL)
        {
            DirtyMemFree(pState->pCertificate, PROTOSSL_MEMID, pState->iMemGroup, pState->pMemGroupUserData);
        }
        pState->pCertificate = _CertificateDecodePublic(pState, (uint8_t *)pValue, iValue);
        return(0);
    }
    if (iSelect == 'snod')
    {
        pState->bNoDelay = (uint8_t)iValue;
        return(0);
    }
    if (iSelect == 'skey')
    {
        if (pState->pPrivateKey != NULL)
        {
            DirtyMemFree(pState->pPrivateKey, PROTOSSL_MEMID, pState->iMemGroup, pState->pMemGroupUserData);
            pState->iPrivateKeyLen = 0;
        }
        if ((pState->pPrivateKey = DirtyMemAlloc(iValue, PROTOSSL_MEMID, pState->iMemGroup, pState->pMemGroupUserData)) != NULL)
        {
            ds_memcpy(pState->pPrivateKey, pValue, iValue);
            pState->iPrivateKeyLen = iValue;
        }
        else
        {
            NetPrintf(("protossl: could not allocate memory for private key\n"));
        }
        return(0);
    }
    if (iSelect == 'skep')
    {
        pState->bKeepAlive = (uint8_t)iValue;
        pState->uKeepAliveTime = (uint32_t)iValue2;
        return(0);
    }
    if (iSelect == 'secu')
    {
        if (pState->iState != ST_UNSECURE)
        {
            NetPrintf(("protossl: cannot promote to a secure connection unless connected in unsecure state\n"));
            return(-1);
        }
        _ProtoSSLResetSecureState(pState, 1);
        pState->iState = ST3_SEND_HELLO;
        return(0);
    }
    if (iSelect == 'spam')
    {
        pState->iVerbose = (int8_t)iValue;
        return(0);
    }
    if (iSelect == 'vers')
    {
        uint32_t uSslVersion = DS_CLAMP(iValue, pState->uSslVersionMin, SSL3_VERSION_MAX);
        if (pState->uSslVersion != uSslVersion)
        {
            NetPrintf(("protossl: setting sslvers to %s (0x%04x)\n", _SSL3_strVersionNames[uSslVersion&0xff], uSslVersion));
            pState->uSslVersion = uSslVersion;
        }
        return(0);
    }
    if (iSelect == 'vmin')
    {
        uint32_t uSslVersionMin = DS_CLAMP(iValue, SSL3_VERSION_MIN, SSL3_VERSION_MAX);
        if (pState->uSslVersionMin != uSslVersionMin)
        {
            NetPrintf(("protossl: setting min sslvers to %s (0x%04x)\n", _SSL3_strVersionNames[uSslVersionMin&0xff], uSslVersionMin));
            pState->uSslVersionMin = uSslVersionMin;
            // make sure requested version is at least minimum version
            ProtoSSLControl(pState, 'vers', pState->uSslVersion, 0, NULL);
        }
        return(0);
    }
    // if we have a socket ref, pass unhandled selector through
    if (pState->pSock != NULL)
    {
        iResult = SocketControl(pState->pSock, iSelect, iValue, pValue, NULL);
    }
    else
    {
        NetPrintf(("protossl: ProtoSSLControl('%C') unhandled\n", iSelect));
    }
    return(iResult);
}

/*F********************************************************************************/
/*!
    \Function ProtoSSLSetCACert

    \Description
        Add one or more X.509 CA certificates to the trusted list. A
        certificate added will be available to all ProtoSSL instances for
        the lifetime of the application. This function can add one or more
        PEM certificates or a single DER certificate.

    \Input *pCertData   - pointer to certificate data (PEM or DER)
    \Input iCertSize    - size of certificate data

    \Output
        int32_t         - negative=error, positive=count of CAs added

    \Notes
        The certificate must be in .DER (binary) or .PEM (base64-encoded)
        format.

    \Version 01/13/2009 (jbrookes)
*/
/********************************************************************************F*/
int32_t ProtoSSLSetCACert(const uint8_t *pCertData, int32_t iCertSize)
{
    return(_ProtoSSLSetCACert(pCertData, iCertSize, TRUE));
}

/*F********************************************************************************/
/*!
    \Function ProtoSSLSetCACert2

    \Description
        Add one or more X.509 CA certificates to the trusted list. A
        certificate added will be available to all ProtoSSL instances for
        the lifetime of the application. This function can add one or more
        PEM certificates or a single DER certificate.

        This version of the function does not validate the CA at load time.
        The X509 certificate data will be copied and retained until the CA
        is validated, either by use of ProtoSSLValidateAllCA() or by the CA
        being used to validate a certificate.

    \Input *pCertData   - pointer to certificate data (PEM or DER)
    \Input iCertSize    - size of certificate data

    \Output
        int32_t         - negative=error, positive=count of CAs added

    \Notes
        The certificate must be in .DER (binary) or .PEM (base64-encoded)
        format.

    \Version 04/21/2011 (jbrookes)
*/
/********************************************************************************F*/
int32_t ProtoSSLSetCACert2(const uint8_t *pCertData, int32_t iCertSize)
{
    return(_ProtoSSLSetCACert(pCertData, iCertSize, FALSE));
}

/*F********************************************************************************/
/*!
    \Function ProtoSSLValidateAllCA

    \Description
        Validate all CA that have been added but not yet been validated.  Validation
        is a one-time process and disposes of the X509 certificate that is retained
        until validation.

    \Output
        int32_t         - zero on success; else the number of certs that could not be validated

    \Version 04/21/2011 (jbrookes)
*/
/********************************************************************************F*/
int32_t ProtoSSLValidateAllCA(void)
{
    ProtoSSLCACertT *pCACert;
    SecureStateT *pSecure;
    void *pMemGroupUserData;
    int32_t iInvalid, iMemGroup;

    // get memgroup settings for certificate blob
    DirtyMemGroupQuery(&iMemGroup, &pMemGroupUserData);

    // allocate secure state for certificate validation
    if ((pSecure = _ProtoSSLAllocSecureState(iMemGroup, pMemGroupUserData)) == NULL)
    {
        NetPrintf(("protossl: could not allocate secure state for ca validation\n"));
        return(-1);
    }

    // validate all installed CA Certs that have not yet been validated
    for (pCACert = _ProtoSSL_CACerts, iInvalid = 0; pCACert != NULL; pCACert = pCACert->pNext)
    {
        // if the CA hasn't been verified already, do that now
        if (pCACert->pX509Cert != NULL)
        {
            if (_ProtoSSLVerifyCertificate(NULL, pSecure, pCACert->pX509Cert, TRUE) == 0)
            {
                #if DIRTYCODE_LOGGING
                char strIdentSubject[512], strIdentIssuer[512];
                NetPrintf(("protossl: ca (%s) validated by ca (%s)\n", _CertificateDebugFormatIdent(&pCACert->pX509Cert->Subject, strIdentSubject, sizeof(strIdentSubject)),
                    _CertificateDebugFormatIdent(&pCACert->pX509Cert->Issuer, strIdentIssuer, sizeof(strIdentIssuer))));
                #endif

                // cert successfully verified
                DirtyMemFree(pCACert->pX509Cert, PROTOSSL_MEMID, pCACert->iMemGroup, pCACert->pMemGroupUserData);
                pCACert->pX509Cert = NULL;
            }
            else
            {
                _CertificateDebugPrint(pCACert->pX509Cert, "ca could not be validated");
                iInvalid += 1;
            }
        }
    }

    // free secure state used for validation
    DirtyMemFree(pSecure, PROTOSSL_MEMID, iMemGroup, pMemGroupUserData);

    // return number of certs we could not validate
    return(iInvalid);
}

/*F********************************************************************************/
/*!
    \Function ProtoSSLClrCACerts

    \Description
        Clears all dynamic CA certs from the list.

    \Version 01/14/2009 (jbrookes)
*/
/********************************************************************************F*/
void ProtoSSLClrCACerts(void)
{
    ProtoSSLCACertT *pCACert, *pCACert0=NULL;

    /*
     * This code makes the following assumptions:
     *    1) There is at least one static cert.
     *    2) All static certs come first, followed by all dynamic certs.
     */

    // scan for first dynamic certificate
    for (pCACert = _ProtoSSL_CACerts; (pCACert != NULL) && (pCACert->iMemGroup == 0); )
    {
        pCACert0 = pCACert;
        pCACert = pCACert->pNext;
    }
    // any dynamic certs?
    if ((pCACert != NULL) && (pCACert0 != NULL))
    {
        // null-terminate static list
        pCACert0->pNext = NULL;

        // delete dynamic certs
        for ( ; pCACert != NULL; )
        {
            pCACert0 = pCACert->pNext;
            if (pCACert->pX509Cert != NULL)
            {
                DirtyMemFree(pCACert->pX509Cert, PROTOSSL_MEMID, pCACert->iMemGroup, pCACert->pMemGroupUserData);
            }
            DirtyMemFree(pCACert, PROTOSSL_MEMID, pCACert->iMemGroup, pCACert->pMemGroupUserData);
            pCACert = pCACert0;
        }
    }
}

/*F********************************************************************************/
/*!
    \Function ProtoSSLPkcs1GenerateInit

    \Description
        Init for generating a PKCSv1.5 RSA signature 

    \Input *pPkcs1      - pkcs1 state
    \Input *pHashData   - hash of the data which was signed
    \Input iHashLen     - length of the hash
    \Input iHashType    - the hash which was used
    \Input iModSize     - size of the modulus
    \Input *pPrimeP     - prime p from the private key
    \Input *pPrimeQ     - prime q from the private key
    \Input *pExponentP  - exponent p from the private key
    \Input *pExponentQ  - exponent q from the private key
    \Input *pCoefficient- coefficient from the private key

    \Notes
        This function will block on the RSA operation.

    \Output
        int32_t         - zero=validation successful, negative=validation failed

    \Version 03/15/2020 (eesponda)
*/
/********************************************************************************F*/
int32_t ProtoSSLPkcs1GenerateInit(ProtoSSLPkcs1T *pPkcs1, const uint8_t *pHashData, int32_t iHashLen, int32_t iHashType, int32_t iModSize, const CryptBinaryObjT *pPrimeP, const CryptBinaryObjT *pPrimeQ, const CryptBinaryObjT *pExponentP, const CryptBinaryObjT *pExponentQ, const CryptBinaryObjT *pCoefficient)
{
    static uint8_t aSigData[SSL_SIG_MAX];
    int32_t iSigSize;

    // generate the signature data
    if ((iSigSize = _AsnWriteDigitalHashObject(aSigData, sizeof(aSigData), pHashData, iHashLen, (CryptHashTypeE)iHashType)) == 0)
    {
        return(-1);
    }
    #if DEBUG_RAW_DATA
    NetPrintMem(pHashData, iHashLen, "message digest");
    #endif

    // init the rsa module with the private key and signature information
    if (CryptRSAInit2(&pPkcs1->RSAContext, iModSize, pPrimeP, pPrimeQ, pExponentP, pExponentQ, pCoefficient))
    {
        return(-1);
    }
    CryptRSAInitPrivate(&pPkcs1->RSAContext, aSigData, iSigSize);
    return(0);
}

/*F********************************************************************************/
/*!
    \Function ProtoSSLPkcs1GenerateUpdate

    \Description
        Generate the PKCSv1.5 RSA signature 

    \Input *pPkcs1          - pkcs1 state
    \Input iNumIterations   - number of iterations to perform or zero to block until complete
    \Input *pSigData        - [out] signature data
    \Input iSigSize         - size of the signature

    \Output
        int32_t         - 1=operation pending, 0=operation complete

    \Version 03/15/2020 (eesponda)
*/
/********************************************************************************F*/
int32_t ProtoSSLPkcs1GenerateUpdate(ProtoSSLPkcs1T *pPkcs1, int32_t iNumIterations, uint8_t *pSigData, int32_t iSigSize)
{
    // perform the encryption rounds
    if (CryptRSAEncrypt(&pPkcs1->RSAContext, iNumIterations) > 0)
    {
        return(1);
    }
    NetPrintfVerbose((DEBUG_ENC_PERF, 0, "protossl: SSL Perf (pkcs1 sig encrypt) %dms\n", pPkcs1->RSAContext.uCryptMsecs));

    #if DEBUG_RAW_DATA
    NetPrintMem(pPkcs1->RSAContext.EncryptBlock, pPkcs1->RSAContext.iKeyModSize, "encrypted signature");
    #endif

    // copy the signature
    ds_memcpy_s(pSigData, iSigSize, pPkcs1->RSAContext.EncryptBlock, pPkcs1->RSAContext.iKeyModSize);
    return(0);
}

/*F********************************************************************************/
/*!
    \Function ProtoSSLPkcs1Verify

    \Description
        Verify the PKCSv1.5 RSA signature 

    \Input *pSigData    - signature data
    \Input iSigLen      - length of the signature
    \Input *pHashData   - hash of the data which was signed
    \Input iHashLen     - length of the hash
    \Input iHashType    - the hash which was used
    \Input *pMod        - public key modulus
    \Input iModSize     - size of the modulus
    \Input *pExp        - public key exponent
    \Input iExpSize     - size of the exponent

    \Notes
        This function will block on the RSA operation.

    \Output
        int32_t         - zero=validation successful, negative=validation failed

    \Version 02/14/2020 (eesponda)
*/
/********************************************************************************F*/
int32_t ProtoSSLPkcs1Verify(const uint8_t *pSigData, int32_t iSigLen, const uint8_t *pHashData, int32_t iHashLen, int32_t iHashType, const uint8_t *pMod, int32_t iModSize, const uint8_t *pExp, int32_t iExpSize)
{
    CryptRSAT RSA;
    int32_t iResult = -1;

    // init the rsa module with the public key and signature information
    if (CryptRSAInit(&RSA, pMod, iModSize, pExp, iExpSize))
    {
        return(-1);
    }
    CryptRSAInitSignature(&RSA, pSigData, iSigLen);

    // perform the encryption rounds
    CryptRSAEncrypt(&RSA, 0);
    NetPrintfVerbose((DEBUG_ENC_PERF, 0, "protossl: SSL Perf (pkcs1 sig verify) %dms\n", RSA.uCryptMsecs));

    #if DEBUG_RAW_DATA
    NetPrintMem(RSA.EncryptBlock, RSA.iKeyModSize, "decrypted signature");
    NetPrintMem(pHashData, iHashLen, "message digest");
    #endif

    // extract hash data from signature block
    if ((pSigData = _Pkcs1VerifyEMSA(RSA.EncryptBlock, iSigLen, iHashLen, (CryptHashTypeE)iHashType)) != NULL)
    {
        // compare hash data with precalculated certificate body hash
        iResult = !memcmp(pHashData, pSigData, iHashLen) ? 0 : -1;
    }

    return(iResult);
}
