Kawe Mazidjatari b3a68ed095 Add EABase, EAThread and DirtySDK to R5sdk
DirtySDK (EA's Dirty Sockets library) will be used for the LiveAPI implementation, and depends on: EABase, EAThread.
2024-04-05 18:29:03 +02:00

14612 lines
552 KiB
C

/*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)
uint8_t bAsyncRecv; //!< if true enable async receive on the ssl socket
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;
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);
}
// if async receive is enabled set it on the socket and adjust the packet queue to fit a large packet (SSL_RCVMAX_PACKET)
if (pState->bAsyncRecv)
{
SocketControl(pState->pSock, 'arcv', TRUE, NULL, NULL);
SocketControl(pState->pSock, 'pque', (SSL_RCVMAX_PACKET / SOCKET_MAXUDPRECV) + 1, 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
'arcv' Set async receive on the ssl socket
'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 == 'arcv')
{
pState->bAsyncRecv = (uint8_t)iValue;
NetPrintfVerbose((pState->iVerbose, 0, "protossl: async recv set to %s\n", pState->bAsyncRecv ? "TRUE" : "FALSE"));
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);
}