mirror of
https://github.com/Mauler125/r5sdk.git
synced 2025-02-09 19:15:03 +01:00
DirtySDK (EA's Dirty Sockets library) will be used for the LiveAPI implementation, and depends on: EABase, EAThread.
14612 lines
552 KiB
C
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);
|
|
}
|