From: Trevor Date: Mon, 13 May 2013 01:55:27 +0000 (-0700) Subject: Add support for arbitrary TLS extensions. X-Git-Tag: master-post-reformat~1296 X-Git-Url: https://git.librecmc.org/?a=commitdiff_plain;h=a398f821fa98b9923a426cf45b268cf4d56c89bd;p=oweals%2Fopenssl.git Add support for arbitrary TLS extensions. Contributed by Trevor Perrin. --- diff --git a/CHANGES b/CHANGES index 63a85fbc1d..73f4a57c86 100644 --- a/CHANGES +++ b/CHANGES @@ -4,6 +4,9 @@ Changes between 1.0.x and 1.1.0 [xx XXX xxxx] + *) Add callbacks for arbitrary TLS extensions. + [Trevor Perrin and Ben Laurie] + *) Support for DTLS 1.2. This adds two sets of DTLS methods: DTLS_*_method() supports both DTLS 1.2 and 1.0 and should use whatever version the peer supports and DTLSv1_2_*_method() which supports DTLS 1.2 only. diff --git a/apps/s_client.c b/apps/s_client.c index 45f6ced044..5fb5dffda7 100644 --- a/apps/s_client.c +++ b/apps/s_client.c @@ -365,6 +365,9 @@ static void sc_usage(void) # ifndef OPENSSL_NO_NEXTPROTONEG BIO_printf(bio_err," -nextprotoneg arg - enable NPN extension, considering named protocols supported (comma-separated list)\n"); # endif +#ifndef OPENSSL_NO_TLSEXT + BIO_printf(bio_err," -serverinfo types - send empty ClientHello extensions (comma-separated numbers)\n"); +#endif #endif BIO_printf(bio_err," -legacy_renegotiation - enable use of legacy renegotiation (dangerous)\n"); BIO_printf(bio_err," -use_srtp profiles - Offer SRTP key management with a colon-separated profile list\n"); @@ -542,6 +545,26 @@ static int next_proto_cb(SSL *s, unsigned char **out, unsigned char *outlen, con return SSL_TLSEXT_ERR_OK; } # endif /* ndef OPENSSL_NO_NEXTPROTONEG */ + +static int serverinfo_cli_cb(SSL* s, unsigned short ext_type, + const unsigned char* in, unsigned short inlen, + int* al, void* arg) + { + char pem_name[100]; + unsigned char ext_buf[4 + 65536]; + + /* Reconstruct the type/len fields prior to extension data */ + ext_buf[0] = ext_type >> 8; + ext_buf[1] = ext_type & 0xFF; + ext_buf[2] = inlen >> 8; + ext_buf[3] = inlen & 0xFF; + memcpy(ext_buf+4, in, inlen); + + BIO_snprintf(pem_name, sizeof(pem_name), "SERVER_INFO %d", ext_type); + PEM_write_bio(bio_c_out, pem_name, "", ext_buf, 4 + inlen); + return 1; + } + #endif enum @@ -614,6 +637,9 @@ int MAIN(int argc, char **argv) # ifndef OPENSSL_NO_NEXTPROTONEG const char *next_proto_neg_in = NULL; # endif +# define MAX_SI_TYPES 100 + unsigned short serverinfo_types[MAX_SI_TYPES]; + int serverinfo_types_count = 0; #endif char *sess_in = NULL; char *sess_out = NULL; @@ -968,6 +994,29 @@ static char *jpake_secret = NULL; next_proto_neg_in = *(++argv); } # endif + else if (strcmp(*argv,"-serverinfo") == 0) + { + char *c; + int start = 0; + int len; + + if (--argc < 1) goto bad; + c = *(++argv); + serverinfo_types_count = 0; + len = strlen(c); + for (i = 0; i <= len; ++i) + { + if (i == len || c[i] == ',') + { + serverinfo_types[serverinfo_types_count] + = atoi(c+start); + serverinfo_types_count++; + start = i+1; + } + if (serverinfo_types_count == MAX_SI_TYPES) + break; + } + } #endif #ifdef FIONBIO else if (strcmp(*argv,"-nbio") == 0) @@ -1261,6 +1310,19 @@ bad: if (next_proto.data) SSL_CTX_set_next_proto_select_cb(ctx, next_proto_cb, &next_proto); #endif +#ifndef OPENSSL_NO_TLSEXT + if (serverinfo_types_count) + { + for (i = 0; i < serverinfo_types_count; i++) + { + SSL_CTX_set_custom_cli_ext(ctx, + serverinfo_types[i], + NULL, + serverinfo_cli_cb, + NULL); + } + } +#endif if (state) SSL_CTX_set_info_callback(ctx,apps_ssl_info_callback); #if 0 diff --git a/apps/s_server.c b/apps/s_server.c index 657b042d07..bf61a8d09e 100644 --- a/apps/s_server.c +++ b/apps/s_server.c @@ -318,6 +318,8 @@ static int cert_chain = 0; #ifndef OPENSSL_NO_TLSEXT static BIO *authz_in = NULL; static const char *s_authz_file = NULL; +static BIO *serverinfo_in = NULL; +static const char *s_serverinfo_file = NULL; #endif #ifndef OPENSSL_NO_PSK @@ -479,6 +481,9 @@ static void sv_usage(void) BIO_printf(bio_err," -cert arg - certificate file to use\n"); BIO_printf(bio_err," (default is %s)\n",TEST_CERT); BIO_printf(bio_err," -authz arg - binary authz file for certificate\n"); +#ifndef OPENSSL_NO_TLSEXT + BIO_printf(bio_err," -serverinfo arg - PEM serverinfo file for certificate\n"); +#endif BIO_printf(bio_err," -crl_check - check the peer certificate has not been revoked by its CA.\n" \ " The CRL(s) are appended to the certificate file\n"); BIO_printf(bio_err," -crl_check_all - check the peer certificate has not been revoked by its CA\n" \ @@ -1093,6 +1098,11 @@ int MAIN(int argc, char *argv[]) if (--argc < 1) goto bad; s_authz_file = *(++argv); } + else if (strcmp(*argv,"-serverinfo") == 0) + { + if (--argc < 1) goto bad; + s_serverinfo_file = *(++argv); + } #endif else if (strcmp(*argv,"-certform") == 0) { @@ -1853,6 +1863,9 @@ bad: #ifndef OPENSSL_NO_TLSEXT if (s_authz_file != NULL && !SSL_CTX_use_authz_file(ctx, s_authz_file)) goto end; + if (s_serverinfo_file != NULL + && !SSL_CTX_use_serverinfo_file(ctx, s_serverinfo_file)) + goto end; #endif #ifndef OPENSSL_NO_TLSEXT if (ctx2 && !set_cert_key_stuff(ctx2,s_cert2,s_key2, NULL, build_chain)) @@ -2032,6 +2045,8 @@ end: EVP_PKEY_free(s_key2); if (authz_in != NULL) BIO_free(authz_in); + if (serverinfo_in != NULL) + BIO_free(serverinfo_in); #endif ssl_excert_free(exc); if (ssl_args) diff --git a/ssl/s3_lib.c b/ssl/s3_lib.c index 7ad8a541fa..67eb92089e 100644 --- a/ssl/s3_lib.c +++ b/ssl/s3_lib.c @@ -3026,6 +3026,8 @@ void ssl3_free(SSL *s) #ifndef OPENSSL_NO_TLSEXT if (s->s3->tlsext_authz_client_types != NULL) OPENSSL_free(s->s3->tlsext_authz_client_types); + if (s->s3->tlsext_custom_types != NULL) + OPENSSL_free(s->s3->tlsext_custom_types); #endif OPENSSL_cleanse(s->s3,sizeof *s->s3); OPENSSL_free(s->s3); @@ -3076,6 +3078,12 @@ void ssl3_clear(SSL *s) OPENSSL_free(s->s3->tlsext_authz_client_types); s->s3->tlsext_authz_client_types = NULL; } + if (s->s3->tlsext_custom_types != NULL) + { + OPENSSL_free(s->s3->tlsext_custom_types); + s->s3->tlsext_custom_types = NULL; + } + s->s3->tlsext_custom_types_count = 0; #endif rp = s->s3->rbuf.buf; diff --git a/ssl/ssl.h b/ssl/ssl.h index e14a8d4fdb..67397cf470 100644 --- a/ssl/ssl.h +++ b/ssl/ssl.h @@ -383,6 +383,56 @@ DECLARE_STACK_OF(SRTP_PROTECTION_PROFILE) typedef int (*tls_session_ticket_ext_cb_fn)(SSL *s, const unsigned char *data, int len, void *arg); typedef int (*tls_session_secret_cb_fn)(SSL *s, void *secret, int *secret_len, STACK_OF(SSL_CIPHER) *peer_ciphers, SSL_CIPHER **cipher, void *arg); +#ifndef OPENSSL_NO_TLSEXT +/* Callbacks and structures for handling custom TLS Extensions: + * cli_ext_first_cb - sends data for ClientHello TLS Extension + * cli_ext_second_cb - receives data from ServerHello TLS Extension + * srv_ext_first_cb - receives data from ClientHello TLS Extension + * srv_ext_second_cb - sends data for ServerHello TLS Extension + * + * All these functions return nonzero on success. Zero will terminate + * the handshake (and return a specific TLS Fatal alert, if the function + * declaration has an "al" parameter). + * + * "ext_type" is a TLS "ExtensionType" from 0-65535. + * "in" is a pointer to TLS "extension_data" being provided to the cb. + * "out" is used by the callback to return a pointer to "extension data" + * which OpenSSL will later copy into the TLS handshake. The contents + * of this buffer should not be changed until the handshake is complete. + * "inlen" and "outlen" are TLS Extension lengths from 0-65535. + * "al" is a TLS "AlertDescription" from 0-255 which WILL be sent as a + * fatal TLS alert, if the callback returns zero. + */ +typedef int (*custom_cli_ext_first_cb_fn)(SSL *s, unsigned short ext_type, + const unsigned char **out, + unsigned short *outlen, void *arg); +typedef int (*custom_cli_ext_second_cb_fn)(SSL *s, unsigned short ext_type, + const unsigned char *in, + unsigned short inlen, int *al, + void *arg); + +typedef int (*custom_srv_ext_first_cb_fn)(SSL *s, unsigned short ext_type, + const unsigned char *in, + unsigned short inlen, int *al, + void *arg); +typedef int (*custom_srv_ext_second_cb_fn)(SSL *s, unsigned short ext_type, + const unsigned char **out, + unsigned short *outlen, void *arg); + +typedef struct { + unsigned short ext_type; + custom_cli_ext_first_cb_fn fn1; + custom_cli_ext_second_cb_fn fn2; + void *arg; +} custom_cli_ext_record; + +typedef struct { + unsigned short ext_type; + custom_srv_ext_first_cb_fn fn1; + custom_srv_ext_second_cb_fn fn2; + void *arg; +} custom_srv_ext_record; +#endif #ifndef OPENSSL_NO_SSL_INTERN @@ -1064,6 +1114,12 @@ struct ssl_ctx_st # endif /* OPENSSL_NO_EC */ int (*tlsext_authz_server_audit_proof_cb)(SSL *s, void *arg); void *tlsext_authz_server_audit_proof_cb_arg; + + /* Arrays containing the callbacks for custom TLS Extensions. */ + custom_cli_ext_record *custom_cli_ext_records; + size_t custom_cli_ext_records_count; + custom_srv_ext_record *custom_srv_ext_records; + size_t custom_srv_ext_records_count; }; #endif @@ -1170,6 +1226,33 @@ const char *SSL_get_psk_identity_hint(const SSL *s); const char *SSL_get_psk_identity(const SSL *s); #endif +#ifndef OPENSSL_NO_TLSEXT +/* Register callbacks to handle custom TLS Extensions as client or server. + * + * Returns nonzero on success. You cannot register twice for the same + * extension number, and registering for an extension number already + * handled by OpenSSL will succeed, but the callbacks will not be invoked. + * + * NULL can be registered for any callback function. For the client + * functions, a NULL custom_cli_ext_first_cb_fn sends an empty ClientHello + * Extension, and a NULL custom_cli_ext_second_cb_fn ignores the ServerHello + * response (if any). + * + * For the server functions, a NULL custom_srv_ext_first_cb_fn means the + * ClientHello extension's data will be ignored, but the extension will still + * be noted and custom_srv_ext_second_cb_fn will still be invoked. If + * custom_srv_ext_second_cb_fn is NULL, an empty ServerHello extension is + * sent. + */ +int SSL_CTX_set_custom_cli_ext(SSL_CTX *ctx, unsigned short ext_type, + custom_cli_ext_first_cb_fn fn1, + custom_cli_ext_second_cb_fn fn2, void *arg); + +int SSL_CTX_set_custom_srv_ext(SSL_CTX *ctx, unsigned short ext_type, + custom_srv_ext_first_cb_fn fn1, + custom_srv_ext_second_cb_fn fn2, void *arg); +#endif + #define SSL_NOTHING 1 #define SSL_WRITING 2 #define SSL_READING 3 @@ -1934,6 +2017,14 @@ const unsigned char *SSL_CTX_get_authz_data(SSL_CTX *ctx, unsigned char type, int SSL_CTX_use_authz_file(SSL_CTX *ctx, const char *file); int SSL_use_authz_file(SSL *ssl, const char *file); #endif + +/* Set serverinfo data for the current active cert. */ +int SSL_CTX_use_serverinfo(SSL_CTX *ctx, const unsigned char *serverinfo, + size_t serverinfo_length); +#ifndef OPENSSL_NO_STDIO +int SSL_CTX_use_serverinfo_file(SSL_CTX *ctx, const char *file); +#endif /* NO_STDIO */ + #endif #ifndef OPENSSL_NO_STDIO @@ -2481,6 +2572,8 @@ void ERR_load_SSL_strings(void); #define SSL_F_SSL_CTX_USE_RSAPRIVATEKEY 177 #define SSL_F_SSL_CTX_USE_RSAPRIVATEKEY_ASN1 178 #define SSL_F_SSL_CTX_USE_RSAPRIVATEKEY_FILE 179 +#define SSL_F_SSL_CTX_USE_SERVERINFO 336 +#define SSL_F_SSL_CTX_USE_SERVERINFO_FILE 337 #define SSL_F_SSL_DO_HANDSHAKE 180 #define SSL_F_SSL_GET_NEW_SESSION 181 #define SSL_F_SSL_GET_PREV_SESSION 217 @@ -2655,6 +2748,7 @@ void ERR_load_SSL_strings(void); #define SSL_R_INVALID_COMPRESSION_ALGORITHM 341 #define SSL_R_INVALID_NULL_CMD_NAME 385 #define SSL_R_INVALID_PURPOSE 278 +#define SSL_R_INVALID_SERVERINFO_DATA 388 #define SSL_R_INVALID_SRP_USERNAME 357 #define SSL_R_INVALID_STATUS_RESPONSE 328 #define SSL_R_INVALID_TICKET_KEYS_LENGTH 325 diff --git a/ssl/ssl3.h b/ssl/ssl3.h index d8ed725d2d..77747c4c1e 100644 --- a/ssl/ssl3.h +++ b/ssl/ssl3.h @@ -577,6 +577,15 @@ typedef struct ssl3_state_st * server echoed our server_authz extension and therefore must send us * a supplemental data handshake message. */ char tlsext_authz_server_promised; + + /* tlsext_custom_types contains an array of TLS Extension types which + * were advertised by the client in its ClientHello, which were not + * otherwise handled by OpenSSL, and which the server has registered + * a custom_srv_ext_record to handle. + * The array does not contain any duplicates, and is in the same order + * as the types were received in the client hello. */ + unsigned short *tlsext_custom_types; + size_t tlsext_custom_types_count; /* how many tlsext_custom_types */ #endif } SSL3_STATE; diff --git a/ssl/ssl_cert.c b/ssl/ssl_cert.c index a0da7d3741..f86511d81c 100644 --- a/ssl/ssl_cert.c +++ b/ssl/ssl_cert.c @@ -329,7 +329,8 @@ CERT *ssl_cert_dup(CERT *cert) } } rpk->valid_flags = 0; - if (cert->pkeys[i].authz != NULL) +#ifndef OPENSSL_NO_TLSEXT + if (cert->pkeys[i].authz != NULL) { /* Just copy everything. */ ret->pkeys[i].authz_length = @@ -339,12 +340,30 @@ CERT *ssl_cert_dup(CERT *cert) if (ret->pkeys[i].authz == NULL) { SSLerr(SSL_F_SSL_CERT_DUP, ERR_R_MALLOC_FAILURE); - return(NULL); + return NULL; } memcpy(ret->pkeys[i].authz, cert->pkeys[i].authz, cert->pkeys[i].authz_length); } + + if (cert->pkeys[i].serverinfo != NULL) + { + /* Just copy everything. */ + ret->pkeys[i].serverinfo_length = + cert->pkeys[i].serverinfo_length; + ret->pkeys[i].serverinfo = + OPENSSL_malloc(ret->pkeys[i].serverinfo_length); + if (ret->pkeys[i].serverinfo == NULL) + { + SSLerr(SSL_F_SSL_CERT_DUP, ERR_R_MALLOC_FAILURE); + return NULL; + } + memcpy(ret->pkeys[i].serverinfo, + cert->pkeys[i].serverinfo, + cert->pkeys[i].serverinfo_length); + } +#endif } ret->references=1; @@ -460,8 +479,16 @@ void ssl_cert_clear_certs(CERT *c) cpk->chain = NULL; } #ifndef OPENSSL_NO_TLSEXT - if (cpk->authz != NULL) + if (cpk->authz) + { OPENSSL_free(cpk->authz); + cpk->authz = NULL; + } + if (cpk->serverinfo) + { + OPENSSL_free(cpk->serverinfo); + cpk->serverinfo = NULL; + } #endif /* Clear all flags apart from explicit sign */ cpk->valid_flags &= CERT_PKEY_EXPLICIT_SIGN; diff --git a/ssl/ssl_err.c b/ssl/ssl_err.c index d8b4d70c13..2a6ae5b993 100644 --- a/ssl/ssl_err.c +++ b/ssl/ssl_err.c @@ -233,6 +233,8 @@ static ERR_STRING_DATA SSL_str_functs[]= {ERR_FUNC(SSL_F_SSL_CTX_USE_RSAPRIVATEKEY), "SSL_CTX_use_RSAPrivateKey"}, {ERR_FUNC(SSL_F_SSL_CTX_USE_RSAPRIVATEKEY_ASN1), "SSL_CTX_use_RSAPrivateKey_ASN1"}, {ERR_FUNC(SSL_F_SSL_CTX_USE_RSAPRIVATEKEY_FILE), "SSL_CTX_use_RSAPrivateKey_file"}, +{ERR_FUNC(SSL_F_SSL_CTX_USE_SERVERINFO), "SSL_CTX_use_serverinfo"}, +{ERR_FUNC(SSL_F_SSL_CTX_USE_SERVERINFO_FILE), "SSL_CTX_use_serverinfo_file"}, {ERR_FUNC(SSL_F_SSL_DO_HANDSHAKE), "SSL_do_handshake"}, {ERR_FUNC(SSL_F_SSL_GET_NEW_SESSION), "ssl_get_new_session"}, {ERR_FUNC(SSL_F_SSL_GET_PREV_SESSION), "ssl_get_prev_session"}, @@ -410,6 +412,7 @@ static ERR_STRING_DATA SSL_str_reasons[]= {ERR_REASON(SSL_R_INVALID_COMPRESSION_ALGORITHM),"invalid compression algorithm"}, {ERR_REASON(SSL_R_INVALID_NULL_CMD_NAME) ,"invalid null cmd name"}, {ERR_REASON(SSL_R_INVALID_PURPOSE) ,"invalid purpose"}, +{ERR_REASON(SSL_R_INVALID_SERVERINFO_DATA),"invalid serverinfo data"}, {ERR_REASON(SSL_R_INVALID_SRP_USERNAME) ,"invalid srp username"}, {ERR_REASON(SSL_R_INVALID_STATUS_RESPONSE),"invalid status response"}, {ERR_REASON(SSL_R_INVALID_TICKET_KEYS_LENGTH),"invalid ticket keys length"}, diff --git a/ssl/ssl_lib.c b/ssl/ssl_lib.c index c3b4032368..b08e82688f 100644 --- a/ssl/ssl_lib.c +++ b/ssl/ssl_lib.c @@ -1697,6 +1697,61 @@ void SSL_CTX_set_next_proto_select_cb(SSL_CTX *ctx, int (*cb) (SSL *s, unsigned ctx->next_proto_select_cb_arg = arg; } # endif + +int SSL_CTX_set_custom_cli_ext(SSL_CTX *ctx, unsigned short ext_type, + custom_cli_ext_first_cb_fn fn1, + custom_cli_ext_second_cb_fn fn2, void* arg) + { + /* Check for duplicates */ + size_t i; + custom_cli_ext_record* record; + + for (i=0; i < ctx->custom_cli_ext_records_count; i++) + if (ext_type == ctx->custom_cli_ext_records[i].ext_type) + return 0; + + ctx->custom_cli_ext_records = OPENSSL_realloc(ctx->custom_cli_ext_records, + (ctx->custom_cli_ext_records_count+1) * sizeof(custom_cli_ext_record)); + if (!ctx->custom_cli_ext_records) { + ctx->custom_cli_ext_records_count = 0; + return 0; + } + ctx->custom_cli_ext_records_count++; + record = &ctx->custom_cli_ext_records[ctx->custom_cli_ext_records_count - 1]; + record->ext_type = ext_type; + record->fn1 = fn1; + record->fn2 = fn2; + record->arg = arg; + return 1; + } + +int SSL_CTX_set_custom_srv_ext(SSL_CTX *ctx, unsigned short ext_type, + custom_srv_ext_first_cb_fn fn1, + custom_srv_ext_second_cb_fn fn2, void* arg) + { + /* Check for duplicates */ + size_t i; + custom_srv_ext_record* record; + + for (i=0; i < ctx->custom_srv_ext_records_count; i++) + if (ext_type == ctx->custom_srv_ext_records[i].ext_type) + return 0; + + ctx->custom_srv_ext_records = OPENSSL_realloc(ctx->custom_srv_ext_records, + (ctx->custom_srv_ext_records_count+1) * sizeof(custom_srv_ext_record)); + if (!ctx->custom_srv_ext_records) { + ctx->custom_srv_ext_records_count = 0; + return 0; + } + ctx->custom_srv_ext_records_count++; + record = &ctx->custom_srv_ext_records[ctx->custom_srv_ext_records_count - 1]; + record->ext_type = ext_type; + record->fn1 = fn1; + record->fn2 = fn2; + record->arg = arg; + return 1; + } + #endif int SSL_export_keying_material(SSL *s, unsigned char *out, size_t olen, @@ -1896,6 +1951,10 @@ SSL_CTX *SSL_CTX_new(const SSL_METHOD *meth) #ifndef OPENSSL_NO_SRP SSL_CTX_SRP_CTX_init(ret); #endif + ret->custom_cli_ext_records = NULL; + ret->custom_cli_ext_records_count = 0; + ret->custom_srv_ext_records = NULL; + ret->custom_srv_ext_records_count = 0; #ifndef OPENSSL_NO_BUF_FREELISTS ret->freelist_max_len = SSL_MAX_BUF_FREELIST_LEN_DEFAULT; ret->rbuf_freelist = OPENSSL_malloc(sizeof(SSL3_BUF_FREELIST)); @@ -2034,6 +2093,10 @@ void SSL_CTX_free(SSL_CTX *a) #ifndef OPENSSL_NO_SRP SSL_CTX_SRP_CTX_free(a); #endif +#ifndef OPENSSL_NO_TLSEXT + OPENSSL_free(a->custom_cli_ext_records); + OPENSSL_free(a->custom_srv_ext_records); +#endif #ifndef OPENSSL_NO_ENGINE if (a->client_cert_engine) ENGINE_finish(a->client_cert_engine); @@ -2479,6 +2542,26 @@ unsigned char *ssl_get_authz_data(SSL *s, size_t *authz_length) return c->pkeys[i].authz; } + +int ssl_get_server_cert_serverinfo(SSL *s, const unsigned char **serverinfo, + size_t *serverinfo_length) + { + CERT *c = NULL; + int i = 0; + *serverinfo_length = 0; + + c = s->cert; + i = ssl_get_server_cert_index(s); + + if (i == -1) + return 0; + if (c->pkeys[i].serverinfo == NULL) + return 0; + + *serverinfo = c->pkeys[i].serverinfo; + *serverinfo_length = c->pkeys[i].serverinfo_length; + return 1; + } #endif void ssl_update_cache(SSL *s,int mode) diff --git a/ssl/ssl_locl.h b/ssl/ssl_locl.h index d06f912343..56f9b4b260 100644 --- a/ssl/ssl_locl.h +++ b/ssl/ssl_locl.h @@ -510,6 +510,14 @@ typedef struct cert_pkey_st * uint8_t data[length]; */ unsigned char *authz; size_t authz_length; + + /* serverinfo data for this certificate. The data is in TLS Extension + * wire format, specifically it's a series of records like: + * uint16_t extension_type; // (RFC 5246, 7.4.1.4, Extension) + * uint16_t length; + * uint8_t data[length]; */ + unsigned char *serverinfo; + size_t serverinfo_length; #endif /* Set if CERT_PKEY can be used with current SSL session: e.g. * appropriate curve, signature algorithms etc. If zero it can't be @@ -1000,7 +1008,11 @@ int ssl_undefined_function(SSL *s); int ssl_undefined_void_function(void); int ssl_undefined_const_function(const SSL *s); CERT_PKEY *ssl_get_server_send_pkey(const SSL *s); +#ifndef OPENSSL_NO_TLSEXT unsigned char *ssl_get_authz_data(SSL *s, size_t *authz_length); +int ssl_get_server_cert_serverinfo(SSL *s, const unsigned char **serverinfo, + size_t *serverinfo_length); +#endif EVP_PKEY *ssl_get_sign_pkey(SSL *s,const SSL_CIPHER *c, const EVP_MD **pmd); int ssl_cert_type(X509 *x,EVP_PKEY *pkey); void ssl_set_cert_masks(CERT *c, const SSL_CIPHER *cipher); diff --git a/ssl/ssl_rsa.c b/ssl/ssl_rsa.c index 1babdef3c6..ac16b8506d 100644 --- a/ssl/ssl_rsa.c +++ b/ssl/ssl_rsa.c @@ -471,6 +471,14 @@ static int ssl_set_cert(CERT *c, X509 *x) c->pkeys[i].authz = NULL; c->pkeys[i].authz_length = 0; } + + /* Free the old serverinfo data, if it exists. */ + if (c->pkeys[i].serverinfo != NULL) + { + OPENSSL_free(c->pkeys[i].serverinfo); + c->pkeys[i].serverinfo = NULL; + c->pkeys[i].serverinfo_length = 0; + } #endif c->key= &(c->pkeys[i]); @@ -855,6 +863,116 @@ static char authz_validate(const unsigned char *authz, size_t length) } } +static int serverinfo_find_extension(const unsigned char *serverinfo, + size_t serverinfo_length, + unsigned short extension_type, + const unsigned char **extension_data, + unsigned short *extension_length) + { + *extension_data = NULL; + *extension_length = 0; + if (serverinfo == NULL || serverinfo_length == 0) + return 0; + for (;;) + { + unsigned short type = 0; /* uint16 */ + unsigned short len = 0; /* uint16 */ + + /* end of serverinfo */ + if (serverinfo_length == 0) + return 0; + + /* read 2-byte type field */ + if (serverinfo_length < 2) + return 0; /* error */ + type = (serverinfo[0] << 8) + serverinfo[1]; + serverinfo += 2; + serverinfo_length -= 2; + + /* read 2-byte len field */ + if (serverinfo_length < 2) + return 0; /* error */ + len = (serverinfo[0] << 8) + serverinfo[1]; + serverinfo += 2; + serverinfo_length -= 2; + + if (len > serverinfo_length) + return 0; /* error */ + + if (type == extension_type) + { + *extension_data = serverinfo; + *extension_length = len; + return 1; + } + + serverinfo += len; + serverinfo_length -= len; + } + return 0; + } + +static int serverinfo_srv_cb(SSL *s, unsigned short ext_type, + const unsigned char **out, unsigned short *outlen, + void *arg) + { + const unsigned char *serverinfo = NULL; + size_t serverinfo_length = 0; + + /* Is there a serverinfo for the chosen server cert? */ + if ((ssl_get_server_cert_serverinfo(s, &serverinfo, + &serverinfo_length)) != 0) + { + /* Find the relevant extension from the serverinfo */ + serverinfo_find_extension(serverinfo, serverinfo_length, + ext_type, out, outlen); + } + return 1; + } + +static int serverinfo_validate(const unsigned char *serverinfo, + size_t serverinfo_length, SSL_CTX *ctx) + { + if (serverinfo == NULL || serverinfo_length == 0) + return 0; + for (;;) + { + unsigned short ext_type = 0; /* uint16 */ + unsigned short len = 0; /* uint16 */ + + /* end of serverinfo */ + if (serverinfo_length == 0) + return 1; + + /* read 2-byte type field */ + if (serverinfo_length < 2) + return 0; + /* FIXME: check for types we understand explicitly? */ + + /* Register callbacks for extensions */ + ext_type = (serverinfo[0] << 8) + serverinfo[1]; + if (ctx && !SSL_CTX_set_custom_srv_ext(ctx, ext_type, NULL, + serverinfo_srv_cb, NULL)) + return 0; + + serverinfo += 2; + serverinfo_length -= 2; + + /* read 2-byte len field */ + if (serverinfo_length < 2) + return 0; + len = (serverinfo[0] << 8) + serverinfo[1]; + serverinfo += 2; + serverinfo_length -= 2; + + if (len > serverinfo_length) + return 0; + + serverinfo += len; + serverinfo_length -= len; + } + } + static const unsigned char *authz_find_data(const unsigned char *authz, size_t authz_length, unsigned char data_type, @@ -906,13 +1024,18 @@ static int ssl_set_authz(CERT *c, unsigned char *authz, size_t authz_length) return(0); } current_key->authz = OPENSSL_realloc(current_key->authz, authz_length); + if (current_key->authz == NULL) + { + SSLerr(SSL_F_SSL_SET_AUTHZ,ERR_R_MALLOC_FAILURE); + return 0; + } current_key->authz_length = authz_length; memcpy(current_key->authz, authz, authz_length); return 1; } int SSL_CTX_use_authz(SSL_CTX *ctx, unsigned char *authz, - size_t authz_length) + size_t authz_length) { if (authz == NULL) { @@ -927,6 +1050,49 @@ int SSL_CTX_use_authz(SSL_CTX *ctx, unsigned char *authz, return ssl_set_authz(ctx->cert, authz, authz_length); } +int SSL_CTX_use_serverinfo(SSL_CTX *ctx, const unsigned char *serverinfo, + size_t serverinfo_length) + { + if (ctx == NULL || serverinfo == NULL || serverinfo_length == 0) + { + SSLerr(SSL_F_SSL_CTX_USE_SERVERINFO,ERR_R_PASSED_NULL_PARAMETER); + return 0; + } + if (!serverinfo_validate(serverinfo, serverinfo_length, NULL)) + { + SSLerr(SSL_F_SSL_CTX_USE_SERVERINFO,SSL_R_INVALID_SERVERINFO_DATA); + return(0); + } + if (!ssl_cert_inst(&ctx->cert)) + { + SSLerr(SSL_F_SSL_CTX_USE_SERVERINFO,ERR_R_MALLOC_FAILURE); + return 0; + } + if (ctx->cert->key == NULL) + { + SSLerr(SSL_F_SSL_CTX_USE_SERVERINFO,ERR_R_INTERNAL_ERROR); + return 0; + } + ctx->cert->key->serverinfo = OPENSSL_realloc(ctx->cert->key->serverinfo, + serverinfo_length); + if (ctx->cert->key->serverinfo == NULL) + { + SSLerr(SSL_F_SSL_CTX_USE_SERVERINFO,ERR_R_MALLOC_FAILURE); + return 0; + } + memcpy(ctx->cert->key->serverinfo, serverinfo, serverinfo_length); + ctx->cert->key->serverinfo_length = serverinfo_length; + + /* Now that the serverinfo is validated and stored, go ahead and + * register callbacks. */ + if (!serverinfo_validate(serverinfo, serverinfo_length, ctx)) + { + SSLerr(SSL_F_SSL_CTX_USE_SERVERINFO,SSL_R_INVALID_SERVERINFO_DATA); + return(0); + } + return 1; + } + int SSL_use_authz(SSL *ssl, unsigned char *authz, size_t authz_length) { if (authz == NULL) @@ -1026,5 +1192,81 @@ int SSL_use_authz_file(SSL *ssl, const char *file) OPENSSL_free(authz); return ret; } + +int SSL_CTX_use_serverinfo_file(SSL_CTX *ctx, const char *file) + { + unsigned char *serverinfo = NULL; + size_t serverinfo_length = 0; + unsigned char* extension = 0; + long extension_length = 0; + char* name = NULL; + char* header = NULL; + int ret = 0; + BIO *bin = NULL; + size_t num_extensions = 0; + + if (ctx == NULL || file == NULL) + { + SSLerr(SSL_F_SSL_CTX_USE_SERVERINFO_FILE,ERR_R_PASSED_NULL_PARAMETER); + goto end; + } + + bin = BIO_new(BIO_s_file_internal()); + if (bin == NULL) + { + SSLerr(SSL_F_SSL_CTX_USE_SERVERINFO_FILE, ERR_R_BUF_LIB); + goto end; + } + if (BIO_read_filename(bin, file) <= 0) + { + SSLerr(SSL_F_SSL_CTX_USE_SERVERINFO_FILE, ERR_R_SYS_LIB); + goto end; + } + + for (num_extensions=0;; num_extensions++) + { + if (PEM_read_bio(bin, &name, &header, &extension, &extension_length) == 0) + { + /* There must be at least one extension in this file */ + if (num_extensions == 0) + { + SSLerr(SSL_F_SSL_CTX_USE_SERVERINFO_FILE, ERR_R_PEM_LIB); + goto end; + } + else /* End of file, we're done */ + break; + } + /* Check that the decoded PEM data is plausible (valid length field) */ + if (extension_length < 4 || (extension[2] << 8) + extension[3] != extension_length - 4) + { + SSLerr(SSL_F_SSL_CTX_USE_SERVERINFO_FILE, ERR_R_PEM_LIB); + goto end; + } + /* Append the decoded extension to the serverinfo buffer */ + serverinfo = OPENSSL_realloc(serverinfo, serverinfo_length + extension_length); + if (serverinfo == NULL) + { + SSLerr(SSL_F_SSL_CTX_USE_SERVERINFO_FILE, ERR_R_MALLOC_FAILURE); + goto end; + } + memcpy(serverinfo + serverinfo_length, extension, extension_length); + serverinfo_length += extension_length; + + OPENSSL_free(name); name = NULL; + OPENSSL_free(header); header = NULL; + OPENSSL_free(extension); extension = NULL; + } + + ret = SSL_CTX_use_serverinfo(ctx, serverinfo, serverinfo_length); +end: + /* SSL_CTX_use_serverinfo makes a local copy of the serverinfo. */ + OPENSSL_free(name); + OPENSSL_free(header); + OPENSSL_free(extension); + OPENSSL_free(serverinfo); + if (bin != NULL) + BIO_free(bin); + return ret; + } #endif /* OPENSSL_NO_STDIO */ #endif /* OPENSSL_NO_TLSEXT */ diff --git a/ssl/ssltest.c b/ssl/ssltest.c index db87b1a35e..f67c7eab8e 100644 --- a/ssl/ssltest.c +++ b/ssl/ssltest.c @@ -370,6 +370,41 @@ static int verify_npn(SSL *client, SSL *server) } #endif +#define SCT_EXT_TYPE 18 +#define TACK_EXT_TYPE 62208 + +/* These set from cmdline */ +char* serverinfo_file = NULL; +int serverinfo_sct = 0; +int serverinfo_tack = 0; +int serverinfo_sct_seen = 0; +int serverinfo_tack_seen = 0; +int serverinfo_other_seen = 0; + +static int serverinfo_cli_cb(SSL* s, unsigned short ext_type, + const unsigned char* in, unsigned short inlen, + int* al, void* arg) + { + if (ext_type == SCT_EXT_TYPE) + serverinfo_sct_seen++; + else if (ext_type == TACK_EXT_TYPE) + serverinfo_tack_seen++; + else + serverinfo_other_seen++; + return 1; + } + +static int verify_serverinfo() + { + if (serverinfo_sct != serverinfo_sct_seen) + return -1; + if (serverinfo_tack != serverinfo_tack_seen) + return -1; + if (serverinfo_other_seen) + return -1; + return 0; + } + static char *cipher=NULL; static int verbose=0; static int debug=0; @@ -449,6 +484,9 @@ static void sv_usage(void) fprintf(stderr," -npn_server - have server side offer NPN\n"); fprintf(stderr," -npn_server_reject - have server reject NPN\n"); #endif + fprintf(stderr," -serverinfo_file - have server use this file\n"); + fprintf(stderr," -serverinfo_sct - have client offer and expect SCT\n"); + fprintf(stderr," -serverinfo_tack - have client offer and expect TACK\n"); } static void print_details(SSL *c_ssl, const char *prefix) @@ -861,6 +899,19 @@ int main(int argc, char *argv[]) npn_server_reject = 1; } #endif + else if (strcmp(*argv,"-serverinfo_sct") == 0) + { + serverinfo_sct = 1; + } + else if (strcmp(*argv,"-serverinfo_tack") == 0) + { + serverinfo_tack = 1; + } + else if (strcmp(*argv,"-serverinfo_file") == 0) + { + if (--argc < 1) goto bad; + serverinfo_file = *(++argv); + } else { fprintf(stderr,"unknown option %s\n",*argv); @@ -1186,6 +1237,18 @@ bad: } #endif + if (serverinfo_sct) + SSL_CTX_set_custom_cli_ext(c_ctx, SCT_EXT_TYPE, NULL, serverinfo_cli_cb, NULL); + if (serverinfo_tack) + SSL_CTX_set_custom_cli_ext(c_ctx, TACK_EXT_TYPE, NULL, serverinfo_cli_cb, NULL); + + if (serverinfo_file) + if (!SSL_CTX_use_serverinfo_file(s_ctx, serverinfo_file)) + { + BIO_printf(bio_err, "missing serverinfo file\n"); + goto end; + } + c_ssl=SSL_new(c_ctx); s_ssl=SSL_new(s_ctx); @@ -1643,6 +1706,12 @@ int doit_biopair(SSL *s_ssl, SSL *c_ssl, long count, goto end; } #endif + if (verify_serverinfo() < 0) + { + ret = 1; + goto err; + } + end: ret = 0; @@ -1945,6 +2014,11 @@ int doit(SSL *s_ssl, SSL *c_ssl, long count) goto err; } #endif + if (verify_serverinfo() < 0) + { + ret = 1; + goto err; + } ret=0; err: /* We have to set the BIO's to NULL otherwise they will be diff --git a/ssl/t1_lib.c b/ssl/t1_lib.c index 31daa50d3e..19769c5888 100644 --- a/ssl/t1_lib.c +++ b/ssl/t1_lib.c @@ -1443,6 +1443,31 @@ unsigned char *ssl_add_clienthello_tlsext(SSL *s, unsigned char *p, unsigned cha *(ret++) = TLSEXT_AUTHZDATAFORMAT_audit_proof; } + /* Add custom TLS Extensions to ClientHello */ + if (s->ctx->custom_cli_ext_records_count) + { + size_t i; + custom_cli_ext_record* record; + + for (i = 0; i < s->ctx->custom_cli_ext_records_count; i++) + { + const unsigned char* out = NULL; + unsigned short outlen = 0; + + record = &s->ctx->custom_cli_ext_records[i]; + if (record->fn1 && !record->fn1(s, record->ext_type, + &out, &outlen, + record->arg)) + return NULL; + if (limit < ret + 4 + outlen) + return NULL; + s2n(record->ext_type, ret); + s2n(outlen, ret); + memcpy(ret, out, outlen); + ret += outlen; + } + } + if ((extdatalen = ret-p-2) == 0) return p; @@ -1709,6 +1734,40 @@ unsigned char *ssl_add_serverhello_tlsext(SSL *s, unsigned char *p, unsigned cha } } + /* If custom types were sent in ClientHello, add ServerHello responses */ + if (s->s3->tlsext_custom_types_count) + { + size_t i; + + for (i = 0; i < s->s3->tlsext_custom_types_count; i++) + { + size_t j; + custom_srv_ext_record *record; + + for (j = 0; j < s->ctx->custom_srv_ext_records_count; j++) + { + record = &s->ctx->custom_srv_ext_records[j]; + if (s->s3->tlsext_custom_types[i] == record->ext_type) + { + const unsigned char *out = NULL; + unsigned short outlen = 0; + if (record->fn2 + && !record->fn2(s, record->ext_type, + &out, &outlen, + record->arg)) + return NULL; + if (limit < ret + 4 + outlen) + return NULL; + s2n(record->ext_type, ret); + s2n(outlen, ret); + memcpy(ret, out, outlen); + ret += outlen; + break; + } + } + } + } + if ((extdatalen = ret-p-2)== 0) return p; @@ -2274,6 +2333,54 @@ static int ssl_scan_clienthello_tlsext(SSL *s, unsigned char **p, unsigned char } } + /* If this ClientHello extension was unhandled and this is + * a nonresumed connection, check whether the extension is a + * custom TLS Extension (has a custom_srv_ext_record), and if + * so call the callback and record the extension number so that + * an appropriate ServerHello may be later returned. + */ + else if (!s->hit && s->ctx->custom_srv_ext_records_count) + { + custom_srv_ext_record *record; + + for (i=0; i < s->ctx->custom_srv_ext_records_count; i++) + { + record = &s->ctx->custom_srv_ext_records[i]; + if (type == record->ext_type) + { + /* Error on duplicate TLS Extensions */ + size_t j; + + for (j = 0; j < s->s3->tlsext_custom_types_count; j++) + { + if (s->s3->tlsext_custom_types[j] == type) + { + *al = TLS1_AD_DECODE_ERROR; + return 0; + } + } + + /* Callback */ + if (record->fn1 && !record->fn1(s, type, data, size, al, record->arg)) + return 0; + + /* Add the (non-duplicated) entry */ + s->s3->tlsext_custom_types_count++; + s->s3->tlsext_custom_types = OPENSSL_realloc( + s->s3->tlsext_custom_types, + s->s3->tlsext_custom_types_count*2); + if (s->s3->tlsext_custom_types == NULL) + { + s->s3->tlsext_custom_types = 0; + *al = TLS1_AD_INTERNAL_ERROR; + return 0; + } + s->s3->tlsext_custom_types[ + s->s3->tlsext_custom_types_count-1] = type; + } + } + } + data+=size; } @@ -2578,6 +2685,26 @@ static int ssl_scan_serverhello_tlsext(SSL *s, unsigned char **p, unsigned char s->s3->tlsext_authz_server_promised = 1; } + + /* If this extension type was not otherwise handled, but + * matches a custom_cli_ext_record, then send it to the c + * callback */ + else if (s->ctx->custom_cli_ext_records_count) + { + size_t i; + custom_cli_ext_record* record; + + for (i = 0; i < s->ctx->custom_cli_ext_records_count; i++) + { + record = &s->ctx->custom_cli_ext_records[i]; + if (record->ext_type == type) + { + if (record->fn2 && !record->fn2(s, type, data, size, al, record->arg)) + return 0; + break; + } + } + } data += size; } diff --git a/test/Makefile b/test/Makefile index f8ffc52f21..52056e28dc 100644 --- a/test/Makefile +++ b/test/Makefile @@ -331,7 +331,7 @@ test_engine: $(ENGINETEST) test_ssl: keyU.ss certU.ss certCA.ss certP1.ss keyP1.ss certP2.ss keyP2.ss \ intP1.ss intP2.ss $(SSLTEST) testssl testsslproxy \ - ../apps/server2.pem + ../apps/server2.pem serverinfo.pem @echo "test SSL protocol" ../util/shlib_wrap.sh ./$(SSLTEST) -test_cipherlist @sh ./testssl keyU.ss certU.ss certCA.ss diff --git a/test/serverinfo.pem b/test/serverinfo.pem new file mode 100644 index 0000000000..0eb020a17f --- /dev/null +++ b/test/serverinfo.pem @@ -0,0 +1,16 @@ +-----BEGIN SCT----- +ABIAZMevsj4TC5rgwjZNciLGwh15YXoIK9t5aypGJIG4QzyMowmwwDdqxudkUcGa +DvuqlYL7psO5j4/BIHTe677CAZBBH3Ho2NOM5q1zub4AbfUMlKeufuQgeQ2Tj1oe +LJLRzrwDnPs= +-----END SCT----- + +-----BEGIN TACK EXTENSION----- +8wABTwFMh1Dz+3W6zULWJKjav5TNaFEXL1h98YtCXeyZnORYg4mbKpxH5CMbjpgx +To3amSqUPF4Ntjc/i9+poutxebYkbgAAAkMcxb8+RaM9YEywaJEGViKJJmpYG/gJ +HgfGaefI9kKbXSDmP9ntg8dLvDzuyYw14ktM2850Q9WvBiltpekilZxVuT2bFtfs +cmS++SAK9YOM8RrKhL1TLmrktoBEJZ6z5GTukYdQ8/t1us1C1iSo2r+UzWhRFy9Y +ffGLQl3smZzkWIOJmyqcR+QjG46YMU6N2pkqlDxeDbY3P4vfqaLrcXm2JG4AAAGN +xXQJPbdniI9rEydVXb1Cu1yT/t7FBEx6hLxuoypXjCI1wCGpXsd8zEnloR0Ank5h +VO/874E/BZlItzSPpcmDKl5Def6BrAJTErQlE9npo52S05YWORxJw1+VYBdqQ09A +x3wA +-----END TACK EXTENSION----- diff --git a/test/testssl b/test/testssl index 242cb0a563..61416885b1 100644 --- a/test/testssl +++ b/test/testssl @@ -30,6 +30,8 @@ else extra="$4" fi +serverinfo="./serverinfo.pem" + ############################################################################# echo test sslv2 @@ -176,6 +178,14 @@ $ssltest -bio_pair -tls1 -npn_client -npn_server || exit 1 $ssltest -bio_pair -tls1 -npn_client -npn_server -num 2 || exit 1 $ssltest -bio_pair -tls1 -npn_client -npn_server -num 2 -reuse || exit 1 +############################################################################# +# Serverinfo tests + +$ssltest -bio_pair -tls1 -serverinfo_file $serverinfo || exit 1 +$ssltest -bio_pair -tls1 -serverinfo_file $serverinfo -serverinfo_sct || exit 1 +$ssltest -bio_pair -tls1 -serverinfo_file $serverinfo -serverinfo_tack || exit 1 +$ssltest -bio_pair -tls1 -serverinfo_file $serverinfo -serverinfo_sct -serverinfo_tack || exit 1 + if ../util/shlib_wrap.sh ../apps/openssl no-srp; then echo skipping SRP tests else diff --git a/util/pl/unix.pl b/util/pl/unix.pl index 40e361b2f9..9c258c4df7 100644 --- a/util/pl/unix.pl +++ b/util/pl/unix.pl @@ -396,6 +396,7 @@ sub get_tests 'testss', 'testssl', 'testsslproxy', + 'serverinfo.pem', ); my $copies = copy_scripts(1, 'test', @copies); $copies .= copy_scripts(0, 'test', ('smcont.txt'));