diff --git a/lib/Makefile.am b/lib/Makefile.am index 5926031d..06e18455 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -34,13 +34,15 @@ lib_LTLIBRARIES = libspdylay.la OBJECTS = spdylay_pq.c spdylay_map.c spdylay_queue.c \ spdylay_buffer.c spdylay_frame.c spdylay_zlib.c \ spdylay_session.c spdylay_helper.c spdylay_stream.c spdylay_npn.c \ - spdylay_submit.c spdylay_outbound_item.c + spdylay_submit.c spdylay_outbound_item.c \ + spdylay_client_cert_vector.c HFILES = spdylay_pq.h spdylay_int.h spdylay_map.h spdylay_queue.h \ spdylay_buffer.h spdylay_frame.h spdylay_zlib.h \ spdylay_session.h spdylay_helper.h spdylay_stream.h spdylay_int.h \ spdylay_npn.h \ spdylay_submit.h spdylay_outbound_item.h \ + spdylay_client_cert_vector.h \ spdylay_net.h libspdylay_la_SOURCES = $(HFILES) $(OBJECTS) diff --git a/lib/includes/spdylay/spdylay.h b/lib/includes/spdylay/spdylay.h index 57d984d5..0fc3a37d 100644 --- a/lib/includes/spdylay/spdylay.h +++ b/lib/includes/spdylay/spdylay.h @@ -154,6 +154,11 @@ typedef enum { * multiple in-sequence NUL bytes). */ SPDYLAY_ERR_INVALID_HEADER_BLOCK = -518, + /** + * Indicates that the context is not suitable to perform the + * requested operation. + */ + SPDYLAY_ERR_INVALID_STATE = -519, /** * The errors < :enum:`SPDYLAY_ERR_FATAL` mean that the library is * under unexpected condition and cannot process any further data @@ -214,7 +219,11 @@ typedef enum { /** * The WINDOW_UPDATE control frame. This first appeared in SPDY/3. */ - SPDYLAY_WINDOW_UPDATE = 9 + SPDYLAY_WINDOW_UPDATE = 9, + /** + * The CREDENTIAL control frame. This first appeared in SPDY/3. + */ + SPDYLAY_CREDENTIAL = 10 } spdylay_frame_type; /** @@ -635,6 +644,52 @@ typedef struct { int32_t delta_window_size; } spdylay_window_update; +/** + * @struct + * + * The structure to hold chunk of memory. + */ +typedef struct { + /** + * The pointer to the data. + */ + uint8_t *data; + /** + * The length of the data. + */ + size_t length; +} spdylay_mem_chunk; + +/** + * @struct + * + * The CREDENTIAL control frame. This first appeared in SPDY/3. It has + * the following members: + */ +typedef struct { + /** + * The control frame header. + */ + spdylay_ctrl_hd hd; + /** + * The index in the client certificate vector. + */ + uint16_t slot; + /** + * Cryptographic proof that the client has possession of the private + * key associated with the certificate. + */ + spdylay_mem_chunk proof; + /** + * The certificate chain. The certs[0] is the leaf certificate. + */ + spdylay_mem_chunk *certs; + /** + * The number of certificates in |certs|. + */ + size_t ncerts; +} spdylay_credential; + /** * @struct * @@ -747,6 +802,10 @@ typedef union { * The WINDOW_UPDATE control frame. */ spdylay_window_update window_update; + /** + * The CREDENTIAL control frame. + */ + spdylay_credential credential; } spdylay_frame; /** @@ -895,54 +954,88 @@ typedef void (*spdylay_on_stream_close_callback) typedef void (*spdylay_on_request_recv_callback) (spdylay_session *session, int32_t stream_id, void *user_data); +#define SPDYLAY_MAX_SCHEME 255 +#define SPDYLAY_MAX_HOSTNAME 255 + +struct spdylay_origin; + +/** + * @struct + * + * The Web origin structure. The origin is the tuple (scheme, host, + * port). The details of this structure is intentionally hidden. To + * access these members, use accessor functions below. + */ +typedef struct spdylay_origin spdylay_origin; + +/** + * @function + * + * Returns the scheme member of the |origin|. + */ +const char* spdylay_origin_get_scheme(const spdylay_origin *origin); + +/** + * @function + * + * Returns the host member of the |origin|. + */ +const char* spdylay_origin_get_host(const spdylay_origin *origin); + +/** + * @function + * + * Returns the port member of the |origin|. + */ +uint16_t spdylay_origin_get_port(const spdylay_origin *origin); + /** * @functypedef * - * Callback function invoked when the library wants to know whether - * the client certificate is required for the given |origin| and if so - * requests the cryptographic proof for the certificate. The |origin| - * is the hostname and port number joined with ':' (e.g., - * example.org:8443). The implementation of this function must assign - * the pointer to the buffer where proof is stored to the |*proof_ptr| - * and its length to the |*prooflen_ptr|. Return 0 if the function - * succeeds. If no client certificate is required for the |origin|, - * the function must return SPDYLAY_ERR_CLIENT_CERT_NOT_NEEDED. - * (TODO: add error code) + * Callback function invoked when the library needs the cryptographic + * proof that the client has possession of the private key associated + * with the certificate for the given |origin|. If called with + * |prooflen| == 0, the implementation of this function must return + * the length of the proof in bytes. If called with |prooflen| > 0, + * write proof into |proof| exactly |prooflen| bytes and return 0. * - * The data stored in |*proof_ptr| will be copied just after the - * function call. This copy lives until the CREDENTIAL frame is - * sent. Because the client certificate vector has limited number of - * slots, the application code may be required to pass the same proof - * more than once. + * Because the client certificate vector has limited number of slots, + * the application code may be required to pass the same proof more + * than once. */ -typedef int (*spdylay_get_credential_proof) -(spdylay_session *session, const char *origin, - uint8_t **proof_ptr, size_t *prooflen_ptr, void *user_data); +typedef ssize_t (*spdylay_get_credential_proof) +(spdylay_session *session, const spdylay_origin *origin, + uint8_t *proof, size_t prooflen, void *user_data); + +/** + * @functypedef + * + * Callback function invoked when the library needs the length of the + * client certificate chain for the given |origin|. The + * implementation of this function must return the length of the + * client certificate chain. If no client certificate is required for + * the given |origin|, return 0. If positive integer is returned, + * :type:`spdylay_get_credential_proof` and + * :type:`spdylay_get_credential_cert` callback functions will be used + * to get the cryptographic proof and certificate respectively. + */ +typedef ssize_t (*spdylay_get_credential_ncerts) +(spdylay_session *session, const spdylay_origin *origin, void *user_data); /** * @functypedef * * Callback function invoked when the library needs the client - * certificate for the given |origin|. The |origin| is the hostname - * and port number joined with ':' (e.g., example.org:8443). The - * implementation of this function must assign the pointer to the - * buffer where certificate is stored to the |*cert_ptr| and its - * length to the |*certlen_ptr|. Because the library requires the - * certificate chain, this callback function will be called repeatedly - * to get certificate chain starting with the leaf certificate. - * Assign 0 to |*certlen_ptr| if there is no more - * certificate. Currently, the library does not expect for this - * function to fail. Therefore the function must return 0. - * - * The data stored in |*cert_ptr| will be copied just after the - * function call. This copy lives until the CREDENTIAL frame is - * sent. Because the client certificate vector has limited number of - * slots, the application code may be required to pass the same - * certificate more than once. + * certificate for the given |origin|. The |index| is the index of the + * certificate chain and 0 means the leaf certificate of the chain. + * If called with |certlen| == 0, the implementation of this function + * must return the length of the certificate in bytes. If called with + * |certlen| > 0, write certificate into |cert| exactly |certlen| + * bytes and return 0. */ -typedef int (*spdylay_get_credential_cert) -(spdylay_session *session, const char* origin, - uint8_t **cert_ptr, size_t *certlen_ptr, void *user_data); +typedef ssize_t (*spdylay_get_credential_cert) +(spdylay_session *session, const spdylay_origin *origin, size_t index, + uint8_t *cert, size_t certlen, void *user_data); /** * @struct @@ -1005,6 +1098,22 @@ typedef struct { * received. */ spdylay_on_request_recv_callback on_request_recv_callback; + /** + * Callback function invoked when the library needs the + * cryptographic proof that the client has possession of the private + * key associated with the certificate. + */ + spdylay_get_credential_proof get_credential_proof; + /** + * Callback function invoked when the library needs the length of the + * client certificate chain. + */ + spdylay_get_credential_ncerts get_credential_ncerts; + /** + * Callback function invoked when the library needs the client + * certificate. + */ + spdylay_get_credential_cert get_credential_cert; } spdylay_session_callbacks; /** @@ -1073,6 +1182,49 @@ int spdylay_session_server_new(spdylay_session **session_ptr, */ void spdylay_session_del(spdylay_session *session); +/** + * @function + * + * Sets the origin tuple (|scheme|, |host| and |port) that the + * connection is made to and the client certificate is sent in the + * first TLS handshake. This function must be called before any call + * of `spdylay_session_send()` and `spdylay_session_recv()` and be + * called only once per session. This function must not be called if + * the |session| is initialized for server use. If the client did not + * provide the client certificate in the first TLS handshake, this + * function must not be called. + * + * This function stores the given origin at the slot 1 in the client + * certificate vector. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * SPDYLAY_ERR_NOMEM + * Out of memory + * SPDYLAY_ERR_INVALID_STATE + * The |session| is initialized for server use; or the client + * certificate vector size is 0. + */ +int spdylay_session_set_initial_client_cert_origin(spdylay_session *session, + const char *scheme, + const char *host, + uint16_t port); + +/** + * @function + * + * Returns the origin at the index |slot| in the client certificate + * vector. If there is no origin at the given |slot|, this function + * returns ``NULL``. + * + * This function must not be called if the |session| is initialized + * for server use. + */ +const spdylay_origin* spdylay_session_get_client_cert_origin +(spdylay_session *session, + size_t slot); + /** * @function * @@ -1094,6 +1246,14 @@ void spdylay_session_del(spdylay_session *session); * :member:`spdylay_session_callbacks.on_ctrl_not_send_callback` is * invoked. Abort the following steps. * 4. If the frame is SYN_STREAM, the stream is opened here. + * If the |session| is initialized for client use and the protocol + * version is :macro:`SPDYLAY_PROTO_SPDY3` and the library needs + * the client certificate for the origin, + * :member:`spdylay_session_callbacks.get_credential_ncerts` is + * invoked. If the result is more than zero, + * :member:`spdylay_session_callbacks.get_credential_proof` and + * :member:`spdylay_session_callbacks.get_credential_cert` are also + * invoked. * 5. :member:`spdylay_session_callbacks.before_ctrl_send_callback` is * invoked. * 6. :member:`spdylay_session_callbacks.send_callback` is invoked one diff --git a/lib/spdylay_client_cert_vector.c b/lib/spdylay_client_cert_vector.c new file mode 100644 index 00000000..ec6c57bd --- /dev/null +++ b/lib/spdylay_client_cert_vector.c @@ -0,0 +1,156 @@ +/* + * Spdylay - SPDY Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "spdylay_client_cert_vector.h" + +#include + +#include "spdylay_helper.h" + +int spdylay_origin_equal(const spdylay_origin *lhs, const spdylay_origin *rhs) +{ + return strcmp(lhs->scheme, rhs->scheme) == 0 && + strcmp(lhs->host, rhs->host) == 0 && lhs->port == rhs->port; +} + +int spdylay_origin_set(spdylay_origin *origin, + const char *scheme, const char *host, uint16_t port) +{ + if(strlen(scheme) > SPDYLAY_MAX_SCHEME || + strlen(host) > SPDYLAY_MAX_HOSTNAME) { + return SPDYLAY_ERR_INVALID_ARGUMENT; + } + strcpy(origin->scheme, scheme); + strcpy(origin->host, host); + origin->port = port; + return 0; +} + +const char* spdylay_origin_get_scheme(const spdylay_origin *origin) +{ + return origin->scheme; +} + +const char* spdylay_origin_get_host(const spdylay_origin *origin) +{ + return origin->host; +} + +uint16_t spdylay_origin_get_port(const spdylay_origin *origin) +{ + return origin->port; +} + +int spdylay_client_cert_vector_init(spdylay_client_cert_vector *certvec, + size_t size) +{ + certvec->size = certvec->capacity = size; + certvec->last_slot = 0; + if(certvec->capacity) { + size_t size = sizeof(spdylay_origin*)*certvec->capacity; + certvec->vector = malloc(size); + if(certvec->vector == NULL) { + return SPDYLAY_ERR_NOMEM; + } + memset(certvec->vector, 0, size); + } else { + certvec->vector = NULL; + } + return 0; +} + +void spdylay_client_cert_vector_free(spdylay_client_cert_vector *certvec) +{ + size_t i; + for(i = 0; i < certvec->size; ++i) { + free(certvec->vector[i]); + } + free(certvec->vector); +} + +int spdylay_client_cert_vector_resize(spdylay_client_cert_vector *certvec, + size_t size) +{ + if(certvec->capacity < size) { + spdylay_origin **vector = realloc(certvec->vector, + sizeof(spdylay_origin*)*size); + if(vector == NULL) { + return SPDYLAY_ERR_NOMEM; + } + memset(vector + certvec->capacity, 0, + sizeof(spdylay_origin*)*(size - certvec->capacity)); + certvec->vector = vector; + certvec->size = certvec->capacity = size; + } else { + size_t i; + for(i = size; i < certvec->size; ++i) { + free(certvec->vector[i]); + certvec->vector[i] = NULL; + } + certvec->size = spdylay_min(certvec->size, size); + if(certvec->last_slot > certvec->size) { + certvec->last_slot = certvec->size; + } + } + return 0; +} + +size_t spdylay_client_cert_vector_find(spdylay_client_cert_vector *certvec, + const spdylay_origin *origin) +{ + size_t i; + for(i = 0; i < certvec->size && certvec->vector[i]; ++i) { + if(spdylay_origin_equal(origin, certvec->vector[i])) { + return i+1; + } + } + return 0; +} + +size_t spdylay_client_cert_vector_put(spdylay_client_cert_vector *certvec, + spdylay_origin *origin) +{ + if(certvec->size == 0) { + return 0; + } + if(certvec->last_slot == certvec->size) { + certvec->last_slot = 1; + } else { + ++certvec->last_slot; + } + free(certvec->vector[certvec->last_slot-1]); + certvec->vector[certvec->last_slot-1] = origin; + return certvec->last_slot; +} + +const spdylay_origin* spdylay_client_cert_vector_get_origin +(spdylay_client_cert_vector *certvec, + size_t slot) +{ + if(slot == 0 || slot > certvec->size || !certvec->vector[slot-1]) { + return NULL; + } else { + return certvec->vector[slot-1]; + } +} diff --git a/lib/spdylay_client_cert_vector.h b/lib/spdylay_client_cert_vector.h new file mode 100644 index 00000000..f47a32cc --- /dev/null +++ b/lib/spdylay_client_cert_vector.h @@ -0,0 +1,112 @@ +/* + * Spdylay - SPDY Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SPDYLAY_CLIENT_CERT_VECTOR_H +#define SPDYLAY_CLIENT_CERT_VECTOR_H + +#ifdef HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +#include + +struct spdylay_origin { + char scheme[SPDYLAY_MAX_SCHEME+1]; + char host[SPDYLAY_MAX_HOSTNAME+1]; + uint16_t port; +}; + +typedef struct { + spdylay_origin **vector; + /* The size of the vector. */ + size_t size; + /* The real capacity of the vector. size <= capacity holds true. */ + size_t capacity; + /* The last slot where origin is stored. The default value is 0. */ + size_t last_slot; +} spdylay_client_cert_vector; + +/* + * Returns nonzero if |lhs| and |rhs| are equal. The equality is + * defined such that each member is equal respectively. + */ +int spdylay_origin_equal(const spdylay_origin *lhs, const spdylay_origin *rhs); + +/* + * Convenient function to set members to the |origin|. The |origin| + * must be allocated prior this call. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * SPDYLAY_ERR_INVALID_ARGUMENT + * The |scheme| is longer than SPDYLAY_MAX_SCHEME; or the |host| + * is longer than SPDYLAY_MAX_HOSTNAME. + */ +int spdylay_origin_set(spdylay_origin *origin, + const char *scheme, const char *host, uint16_t port); + +/* + * Initializes the client certificate vector with the vector size + * |size|. + */ +int spdylay_client_cert_vector_init(spdylay_client_cert_vector *certvec, + size_t size); + +void spdylay_client_cert_vector_free(spdylay_client_cert_vector *certvec); + +/* + * Returns the slot of the |origin| in the client certificate vector. + * If it is not found, returns 0. + */ +size_t spdylay_client_cert_vector_find(spdylay_client_cert_vector *certvec, + const spdylay_origin *origin); + +/* + * Puts the |origin| to the |certvec|. This function takes ownership + * of the |origin| on success. + * + * This function returns the positive slot index of the certificate + * vector where the |origin| is stored if it succeeds, or 0. + */ +size_t spdylay_client_cert_vector_put(spdylay_client_cert_vector *certvec, + spdylay_origin *origin); + +/* + * Resizes client certificate vector to the size |size|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * SPDYLAY_ERR_NOMEM + * Out of memory. + */ +int spdylay_client_cert_vector_resize(spdylay_client_cert_vector *certvec, + size_t size); + +const spdylay_origin* spdylay_client_cert_vector_get_origin +(spdylay_client_cert_vector *certvec, + size_t slot); + +#endif /* SPDYLAY_CLIENT_CERT_VECTOR_H */ diff --git a/lib/spdylay_frame.c b/lib/spdylay_frame.c index 1b2ccb9a..612bc7f9 100644 --- a/lib/spdylay_frame.c +++ b/lib/spdylay_frame.c @@ -27,6 +27,7 @@ #include #include #include +#include #include "spdylay_helper.h" #include "spdylay_net.h" @@ -500,6 +501,52 @@ void spdylay_frame_nv_2to3(char **nv) } } +#define SPDYLAY_HTTPS_PORT 443 + +int spdylay_frame_nv_set_origin(char **nv, spdylay_origin *origin) +{ + int scheme_found, host_found; + int i; + scheme_found = host_found = 0; + for(i = 0; nv[i]; i += 2) { + if(scheme_found == 0 && strcmp(":scheme", nv[i]) == 0) { + size_t len = strlen(nv[i+1]); + if(len <= SPDYLAY_MAX_SCHEME) { + strcpy(origin->scheme, nv[i+1]); + scheme_found = 1; + } + } else if(host_found == 0 && strcmp(":host", nv[i]) == 0) { + size_t len = strlen(nv[i+1]); + char *sep = memchr(nv[i+1], ':', len); + size_t hostlen; + if(sep == NULL) { + origin->port = SPDYLAY_HTTPS_PORT; + sep = nv[i+1]+len; + } else { + unsigned long int port; + errno = 0; + port = strtoul(sep+1, NULL, 10); + if(errno != 0 || port == 0 || port > UINT16_MAX) { + continue; + } + origin->port = port; + } + hostlen = sep-nv[i+1]; + if(hostlen > SPDYLAY_MAX_HOSTNAME) { + continue; + } + memcpy(origin->host, nv[i+1], hostlen); + origin->host[hostlen] = '\0'; + host_found = 1; + } + } + if(scheme_found && host_found) { + return 0; + } else { + return SPDYLAY_ERR_INVALID_ARGUMENT; + } +} + void spdylay_frame_syn_stream_init(spdylay_syn_stream *frame, uint16_t version, uint8_t flags, int32_t stream_id, int32_t assoc_stream_id, @@ -640,6 +687,36 @@ void spdylay_frame_settings_free(spdylay_settings *frame) free(frame->iv); } +void spdylay_frame_credential_init(spdylay_credential *frame, + uint16_t version, uint16_t slot, + spdylay_mem_chunk *proof, + spdylay_mem_chunk *certs, + size_t ncerts) +{ + size_t i; + memset(frame, 0, sizeof(spdylay_credential)); + frame->hd.version = version; + frame->hd.type = SPDYLAY_CREDENTIAL; + frame->slot = slot; + frame->proof = *proof; + frame->certs = certs; + frame->ncerts = ncerts; + frame->hd.length = 2+4+frame->proof.length; + for(i = 0; i < ncerts; ++i) { + frame->hd.length += 4+frame->certs[i].length; + } +} + +void spdylay_frame_credential_free(spdylay_credential *frame) +{ + size_t i; + free(frame->proof.data); + for(i = 0; i < frame->ncerts; ++i) { + free(frame->certs[i].data); + } + free(frame->certs); +} + void spdylay_frame_data_init(spdylay_data *frame, int32_t stream_id, uint8_t flags, const spdylay_data_provider *data_prd) @@ -1076,6 +1153,158 @@ int spdylay_frame_unpack_settings(spdylay_settings *frame, return 0; } +ssize_t spdylay_frame_pack_credential(uint8_t **buf_ptr, size_t *buflen_ptr, + spdylay_credential *frame) +{ + ssize_t framelen; + int r; + size_t i, offset; + framelen = SPDYLAY_FRAME_HEAD_LENGTH+2+4+frame->proof.length; + for(i = 0; i < frame->ncerts; ++i) { + framelen += 4+frame->certs[i].length; + } + r = spdylay_reserve_buffer(buf_ptr, buflen_ptr, framelen); + if(r != 0) { + return r; + } + memset(*buf_ptr, 0, framelen); + spdylay_frame_pack_ctrl_hd(*buf_ptr, &frame->hd); + offset = SPDYLAY_FRAME_HEAD_LENGTH; + spdylay_put_uint16be(&(*buf_ptr)[offset], frame->slot); + offset += 2; + spdylay_put_uint32be(&(*buf_ptr)[offset], frame->proof.length); + offset += 4; + memcpy(&(*buf_ptr)[offset], frame->proof.data, frame->proof.length); + offset += frame->proof.length; + for(i = 0; i < frame->ncerts; ++i) { + spdylay_put_uint32be(&(*buf_ptr)[offset], frame->certs[i].length); + offset += 4; + memcpy(&(*buf_ptr)[offset], frame->certs[i].data, frame->certs[i].length); + offset += frame->certs[i].length; + } + return framelen; +} + +/* + * Counts number of client certificate in CREDENTIAL frame payload + * |payload| with length |payloadlen|. The |payload| points to the + * length of the first certificate. This function also checks the + * frame payload is properly composed. + * + * This function returns the number of certificates in |payload| if it + * succeeds, or one of the following negative error codes: + * + * SPDYLAY_ERR_INVALID_FRAME + * The frame payload is invalid. + */ +static int spdylay_frame_count_unpack_cert(const uint8_t *payload, + size_t payloadlen) +{ + size_t n, offset = 0; + for(n = 1; 1; ++n) { + size_t len; + if(offset+4 > payloadlen) { + return SPDYLAY_ERR_INVALID_FRAME; + } + len = spdylay_get_uint32(&payload[offset]); + offset += 4; + if(len > payloadlen || offset+len > payloadlen) { + return SPDYLAY_ERR_INVALID_FRAME; + } else { + offset += len; + if(offset == payloadlen) { + break; + } + } + } + return n; +} + +/* + * Unpacks client certificates in the |payload| with length + * |payloadlen|. First allocates memory to store the |ncerts| + * certificates. Stores certificates in the allocated space and set + * its pointer to |*certs_ptr|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * SPDYLAY_ERR_NOMEM + * Out of memory + */ +static int spdylay_frame_unpack_cert(spdylay_mem_chunk **certs_ptr, + size_t ncerts, + const uint8_t *payload, size_t payloadlen) +{ + size_t offset, i, j; + spdylay_mem_chunk *certs; + certs = malloc(sizeof(spdylay_mem_chunk)*ncerts); + if(certs == NULL) { + return SPDYLAY_ERR_NOMEM; + } + offset = 0; + for(i = 0; i < ncerts; ++i) { + certs[i].length = spdylay_get_uint32(&payload[offset]); + offset += 4; + certs[i].data = malloc(certs[i].length); + if(certs[i].data == NULL) { + goto fail; + } + memcpy(certs[i].data, &payload[offset], certs[i].length); + offset += certs[i].length; + } + *certs_ptr = certs; + return 0; + fail: + for(j = 0; j < i; ++j) { + free(certs[j].data); + } + free(certs); + return SPDYLAY_ERR_NOMEM; +} + +int spdylay_frame_unpack_credential(spdylay_credential *frame, + const uint8_t *head, size_t headlen, + const uint8_t *payload, + size_t payloadlen) +{ + size_t offset; + int rv; + if(payloadlen < 10) { + return SPDYLAY_ERR_INVALID_FRAME; + } + spdylay_frame_unpack_ctrl_hd(&frame->hd, head); + offset = 0; + frame->slot = spdylay_get_uint16(&payload[offset]); + offset += 2; + frame->proof.length = spdylay_get_uint32(&payload[offset]); + offset += 4; + if(frame->proof.length > payloadlen || + offset+frame->proof.length > payloadlen) { + return SPDYLAY_ERR_INVALID_FRAME; + } + frame->proof.data = malloc(frame->proof.length); + if(frame->proof.data == NULL) { + return SPDYLAY_ERR_NOMEM; + } + memcpy(frame->proof.data, &payload[offset], frame->proof.length); + offset += frame->proof.length; + rv = spdylay_frame_count_unpack_cert(payload+offset, payloadlen-offset); + if(rv < 0) { + goto fail; + } + frame->ncerts = rv; + rv = spdylay_frame_unpack_cert(&frame->certs, frame->ncerts, + payload+offset, payloadlen-offset); + if(rv != 0) { + goto fail; + } + return 0; + fail: + free(frame->proof.data); + return rv; +} + spdylay_settings_entry* spdylay_frame_iv_copy(const spdylay_settings_entry *iv, size_t niv) { diff --git a/lib/spdylay_frame.h b/lib/spdylay_frame.h index da8dce0e..e06f7da3 100644 --- a/lib/spdylay_frame.h +++ b/lib/spdylay_frame.h @@ -32,6 +32,7 @@ #include #include "spdylay_zlib.h" #include "spdylay_buffer.h" +#include "spdylay_client_cert_vector.h" #define SPDYLAY_STREAM_ID_MASK 0x7fffffff #define SPDYLAY_LENGTH_MASK 0xffffff @@ -443,6 +444,35 @@ int spdylay_frame_unpack_settings(spdylay_settings *frame, const uint8_t *head, size_t headlen, const uint8_t *payload, size_t payloadlen); +/* + * Packs CREDENTIAL frame |frame| in wire format and store it in + * |*buf_ptr|. The capacity of |*buf_ptr| is |*buflen_ptr| + * length. This function expands |*buf_ptr| as necessary to store + * given |frame|. + * + * This function returns the size of packed frame if it succeeds, or + * returns one of the following negative error codes: + * + * SPDYLAY_ERR_NOMEM + * Out of memory. + */ +ssize_t spdylay_frame_pack_credential(uint8_t **buf_ptr, size_t *buflen_ptr, + spdylay_credential *frame); + +/* + * Unpacks CREDENTIAL wire format into |frame|. + * + * This function returns 0 if it succeeds or one of the following + * negative error codes: + * + * SPDYLAY_ERR_INVALID_FRAME + * The input data are invalid. + * SPDYLAY_ERR_NOMEM + * Out of memory. + */ +int spdylay_frame_unpack_credential(spdylay_credential *frame, + const uint8_t *head, size_t headlen, + const uint8_t *payload, size_t payloadlen); /* * Returns number of bytes to pack name/value pairs |nv|. This * function expects |nv| is sorted in ascending order of key. @@ -661,6 +691,19 @@ void spdylay_frame_settings_init(spdylay_settings *frame, void spdylay_frame_settings_free(spdylay_settings *frame); +/* + * Initializes CREDENTIAL frame |frame| with given values. This + * function takes ownership of |proof->data| and |certs| on success. + * Note that the ownership of |proof| is not taken. + */ +void spdylay_frame_credential_init(spdylay_credential *frame, + uint16_t version, uint16_t slot, + spdylay_mem_chunk *proof, + spdylay_mem_chunk *certs, + size_t ncerts); + +void spdylay_frame_credential_free(spdylay_credential *frame); + void spdylay_frame_data_init(spdylay_data *frame, int32_t stream_id, uint8_t flags, const spdylay_data_provider *data_prd); @@ -716,6 +759,24 @@ void spdylay_frame_nv_3to2(char **nv); */ void spdylay_frame_nv_2to3(char **nv); +/* + * Assigns the members of the |origin| using ":scheme" and ":host" + * values in |nv|. + * + * If ":host" value contains ':', this function parses the chracters + * after ':' as integer and uses it as port number. + * + * If ':' is missing in :host value, the default port number is used. + * The only defined default port number is 443. + * + * This function returns 0 if it succeeds, or one of the following + * negative error code: + * + * SPDYLAY_ERR_INVALID_ARGUMENT + * The |nv| lacks either :scheme or :host, or both. + */ +int spdylay_frame_nv_set_origin(char **nv, spdylay_origin *origin); + /* * Makes copy of |iv| and return the copy. The |niv| is the number of * entries in |iv|. This function returns the pointer to the copy if diff --git a/lib/spdylay_int.h b/lib/spdylay_int.h index 1f7924af..de030de1 100644 --- a/lib/spdylay_int.h +++ b/lib/spdylay_int.h @@ -35,4 +35,10 @@ typedef int (*spdylay_compar)(const void *lhs, const void *rhs); +/* Internal error code. They must be in the range [-499, -100], + inclusive. */ +typedef enum { + SPDYLAY_ERR_CREDENTIAL_PENDING = -100 +} spdylay_internal_error; + #endif /* SPDYLAY_INT_H */ diff --git a/lib/spdylay_outbound_item.c b/lib/spdylay_outbound_item.c index 239def6e..51e854eb 100644 --- a/lib/spdylay_outbound_item.c +++ b/lib/spdylay_outbound_item.c @@ -66,6 +66,9 @@ void spdylay_outbound_item_free(spdylay_outbound_item *item) case SPDYLAY_WINDOW_UPDATE: spdylay_frame_window_update_free(&frame->window_update); break; + case SPDYLAY_CREDENTIAL: + spdylay_frame_credential_free(&frame->credential); + break; } } else if(item->frame_cat == SPDYLAY_DATA) { spdylay_data *data_frame; diff --git a/lib/spdylay_outbound_item.h b/lib/spdylay_outbound_item.h index 12067260..12db6a6f 100644 --- a/lib/spdylay_outbound_item.h +++ b/lib/spdylay_outbound_item.h @@ -32,6 +32,13 @@ #include #include "spdylay_frame.h" +/* Priority for PING */ +#define SPDYLAY_OB_PRI_PING -10 +/* Priority for CREDENTIAL */ +#define SPDYLAY_OB_PRI_CREDENTIAL -2 +/* Priority for the frame which must be sent after CREDENTIAL */ +#define SPDYLAY_OB_PRI_AFTER_CREDENTIAL -1 + typedef struct { spdylay_data_provider *data_prd; void *stream_user_data; diff --git a/lib/spdylay_session.c b/lib/spdylay_session.c index 51b149cc..aaaf2551 100644 --- a/lib/spdylay_session.c +++ b/lib/spdylay_session.c @@ -114,7 +114,8 @@ static void spdylay_inbound_frame_reset(spdylay_inbound_frame *iframe) static int spdylay_session_new(spdylay_session **session_ptr, uint16_t version, const spdylay_session_callbacks *callbacks, - void *user_data) + void *user_data, + size_t cli_certvec_length) { int r; if(version != SPDYLAY_PROTO_SPDY2 && version != SPDYLAY_PROTO_SPDY3) { @@ -205,8 +206,16 @@ static int spdylay_session_new(spdylay_session **session_ptr, (*session_ptr)->iframe.bufmax = SPDYLAY_INITIAL_INBOUND_FRAMEBUF_LENGTH; spdylay_inbound_frame_reset(&(*session_ptr)->iframe); + r = spdylay_client_cert_vector_init(&(*session_ptr)->cli_certvec, + cli_certvec_length); + if(r != 0) { + goto fail_client_cert_vector; + } + return 0; + fail_client_cert_vector: + free((*session_ptr)->iframe.buf); fail_iframe_buf: free((*session_ptr)->nvbuf); fail_nvbuf: @@ -232,7 +241,8 @@ int spdylay_session_client_new(spdylay_session **session_ptr, void *user_data) { int r; - r = spdylay_session_new(session_ptr, version, callbacks, user_data); + r = spdylay_session_new(session_ptr, version, callbacks, user_data, + SPDYLAY_INITIAL_CLIENT_CERT_VECTOR_LENGTH); if(r == 0) { /* IDs for use in client */ (*session_ptr)->next_stream_id = 1; @@ -248,7 +258,8 @@ int spdylay_session_server_new(spdylay_session **session_ptr, void *user_data) { int r; - r = spdylay_session_new(session_ptr, version, callbacks, user_data); + r = spdylay_session_new(session_ptr, version, callbacks, user_data, + 0); if(r == 0) { (*session_ptr)->server = 1; /* IDs for use in client */ @@ -301,6 +312,7 @@ void spdylay_session_del(spdylay_session *session) free(session->nvbuf); spdylay_buffer_free(&session->inflatebuf); free(session->iframe.buf); + spdylay_client_cert_vector_free(&session->cli_certvec); free(session); } @@ -354,8 +366,8 @@ int spdylay_session_add_frame(spdylay_session *session, unreachable. */ assert(0); case SPDYLAY_PING: - /* Ping has "height" priority. Give it -1. */ - item->pri = -1; + /* Ping has highest priority. */ + item->pri = SPDYLAY_OB_PRI_PING; break; case SPDYLAY_GOAWAY: /* Should GOAWAY have higher priority? */ @@ -376,6 +388,9 @@ int spdylay_session_add_frame(spdylay_session *session, } break; } + case SPDYLAY_CREDENTIAL: + item->pri = SPDYLAY_OB_PRI_CREDENTIAL; + break; } if(frame_type == SPDYLAY_SYN_STREAM) { r = spdylay_pq_push(&session->ob_ss_pq, item); @@ -732,6 +747,146 @@ static int spdylay_session_predicate_data_send(spdylay_session *session, } } +/* + * Retrieves cryptographic proof for the given |origin| using callback + * function and store it in |proof|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * SPDYLAY_ERR_NOMEM + * Out of memory. + */ +static int spdylay_session_get_credential_proof(spdylay_session *session, + const spdylay_origin *origin, + spdylay_mem_chunk *proof) +{ + proof->length = session->callbacks.get_credential_proof(session, + origin, + NULL, 0, + session->user_data); + proof->data = malloc(proof->length); + if(proof->data == NULL) { + return SPDYLAY_ERR_NOMEM; + } + session->callbacks.get_credential_proof(session, origin, + proof->data, proof->length, + session->user_data); + return 0; +} + +/* + * Retrieves client certificate chain for the given |origin| using + * callback functions and store the pointer to the allocated buffer + * containing certificate chain to |*certs_ptr|. The length of + * certificate chain is exactly |ncerts|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * SPDYLAY_ERR_NOMEM + * Out of memory. + */ +static int spdylay_session_get_credential_cert(spdylay_session *session, + const spdylay_origin *origin, + spdylay_mem_chunk **certs_ptr, + size_t ncerts) +{ + spdylay_mem_chunk *certs; + size_t i, j; + certs = malloc(sizeof(spdylay_mem_chunk)*ncerts); + if(certs == NULL) { + return SPDYLAY_ERR_NOMEM; + } + for(i = 0; i < ncerts; ++i) { + certs[i].length = session->callbacks.get_credential_cert + (session, + origin, + i, NULL, 0, + session->user_data); + certs[i].data = malloc(certs[i].length); + if(certs[i].data == NULL) { + goto fail; + } + session->callbacks.get_credential_cert(session, origin, i, + certs[i].data, certs[i].length, + session->user_data); + } + *certs_ptr = certs; + return 0; + fail: + for(j = 0; j < i; ++j) { + free(certs[i].data); + } + free(certs); + return SPDYLAY_ERR_NOMEM; +} + +int spdylay_session_prep_credential(spdylay_session *session, + spdylay_syn_stream *syn_stream) +{ + int rv; + spdylay_origin origin; + spdylay_frame *frame; + spdylay_mem_chunk proof; + if(session->cli_certvec.size == 0) { + return 0; + } + rv = spdylay_frame_nv_set_origin(syn_stream->nv, &origin); + if(rv == 0) { + size_t slot; + slot = spdylay_client_cert_vector_find(&session->cli_certvec, &origin); + if(slot == 0) { + ssize_t ncerts; + ncerts = session->callbacks.get_credential_ncerts(session, &origin, + session->user_data); + if(ncerts > 0) { + spdylay_mem_chunk *certs; + spdylay_origin *origin_copy; + frame = malloc(sizeof(spdylay_frame)); + if(frame == NULL) { + return SPDYLAY_ERR_NOMEM; + } + origin_copy = malloc(sizeof(spdylay_origin)); + if(origin_copy == NULL) { + goto fail_after_frame; + } + *origin_copy = origin; + slot = spdylay_client_cert_vector_put(&session->cli_certvec, + origin_copy); + + rv = spdylay_session_get_credential_proof(session, &origin, &proof); + if(rv != 0) { + goto fail_after_frame; + } + rv = spdylay_session_get_credential_cert(session, &origin, + &certs, ncerts); + if(rv != 0) { + goto fail_after_proof; + } + spdylay_frame_credential_init(&frame->credential, + session->version, + slot, &proof, certs, ncerts); + rv = spdylay_session_add_frame(session, SPDYLAY_CTRL, frame, NULL); + if(rv != 0) { + spdylay_frame_credential_free(&frame->credential); + return rv; + } + return SPDYLAY_ERR_CREDENTIAL_PENDING; + } + } else { + return slot; + } + } + return 0; + fail_after_proof: + free(proof.data); + fail_after_frame: + free(frame); + return rv; + +} + static ssize_t spdylay_session_prep_frame(spdylay_session *session, spdylay_outbound_item *item) { @@ -751,8 +906,30 @@ static ssize_t spdylay_session_prep_frame(spdylay_session *session, if(r != 0) { return r; } + if(session->version == SPDYLAY_PROTO_SPDY3 && + session->callbacks.get_credential_proof && + session->callbacks.get_credential_cert && + !session->server) { + int slot_index; + slot_index = spdylay_session_prep_credential(session, + &frame->syn_stream); + if(slot_index == SPDYLAY_ERR_CREDENTIAL_PENDING) { + /* CREDENTIAL frame has been queued. SYN_STREAM has to be + sent after that. Change the priority of this item to + achieve this. */ + item->pri = SPDYLAY_OB_PRI_AFTER_CREDENTIAL; + r = spdylay_pq_push(&session->ob_ss_pq, item); + if(r == 0) { + return SPDYLAY_ERR_CREDENTIAL_PENDING; + } else { + return r; + } + } else if(r < 0) { + return r; + } + frame->syn_stream.slot = slot_index; + } stream_id = session->next_stream_id; - frame->syn_stream.stream_id = stream_id; session->next_stream_id += 2; if(session->version == SPDYLAY_PROTO_SPDY2) { @@ -892,6 +1069,15 @@ static ssize_t spdylay_session_prep_frame(spdylay_session *session, return framebuflen; } break; + case SPDYLAY_CREDENTIAL: { + framebuflen = spdylay_frame_pack_credential(&session->aob.framebuf, + &session->aob.framebufmax, + &frame->credential); + if(framebuflen < 0) { + return framebuflen; + } + break; + } default: framebuflen = SPDYLAY_ERR_INVALID_ARGUMENT; } @@ -1134,6 +1320,8 @@ static int spdylay_session_after_frame_sent(spdylay_session *session) } case SPDYLAY_WINDOW_UPDATE: break; + case SPDYLAY_CREDENTIAL: + break; } spdylay_active_outbound_item_reset(&session->aob); } else if(item->frame_cat == SPDYLAY_DATA) { @@ -1235,7 +1423,8 @@ int spdylay_session_send(spdylay_session *session) break; } framebuflen = spdylay_session_prep_frame(session, item); - if(framebuflen == SPDYLAY_ERR_DEFERRED) { + if(framebuflen == SPDYLAY_ERR_DEFERRED || + framebuflen == SPDYLAY_ERR_CREDENTIAL_PENDING) { continue; } else if(framebuflen < 0) { if(item->frame_cat == SPDYLAY_CTRL && @@ -1624,7 +1813,7 @@ int spdylay_session_on_settings_received(spdylay_session *session, /* Check ID/value pairs and persist them if necessary. */ memset(check, 0, sizeof(check)); for(i = 0; i < frame->settings.niv; ++i) { - const spdylay_settings_entry *entry = &frame->settings.iv[i]; + spdylay_settings_entry *entry = &frame->settings.iv[i]; /* SPDY/3 spec says if the multiple values for the same ID were found, use the first one and ignore the rest. */ if(entry->settings_id > SPDYLAY_SETTINGS_MAX || entry->settings_id == 0 || @@ -1639,6 +1828,19 @@ int spdylay_session_on_settings_received(spdylay_session *session, if(entry->value < (1u << 31)) { spdylay_session_update_initial_window_size(session, entry->value); } + } else if(entry->settings_id == + SPDYLAY_SETTINGS_CLIENT_CERTIFICATE_VECTOR_SIZE) { + if(!session->server) { + int rv; + /* Limit certificate vector length in the reasonable size. */ + entry->value = spdylay_min(entry->value, + SPDYLAY_MAX_CLIENT_CERT_VECTOR_LENGTH); + rv = spdylay_client_cert_vector_resize(&session->cli_certvec, + entry->value); + if(rv != 0) { + return rv; + } + } } session->remote_settings[entry->settings_id] = entry->value; } @@ -1721,6 +1923,19 @@ int spdylay_session_on_window_update_received(spdylay_session *session, return 0; } +int spdylay_session_on_credential_received(spdylay_session *session, + spdylay_frame *frame) +{ + if(!spdylay_session_check_version(session, frame->credential.hd.version)) { + return 0; + } + /* We don't care about the body of the CREDENTIAL frame. It is left + to the application code to decide it is invalid or not. */ + spdylay_session_call_on_ctrl_frame_received(session, SPDYLAY_CREDENTIAL, + frame); + return 0; +} + int spdylay_session_on_headers_received(spdylay_session *session, spdylay_frame *frame) { @@ -1930,6 +2145,19 @@ static int spdylay_session_process_ctrl_frame(spdylay_session *session) r = spdylay_session_fail_session(session, SPDYLAY_GOAWAY_PROTOCOL_ERROR); } break; + case SPDYLAY_CREDENTIAL: + r = spdylay_frame_unpack_credential(&frame.credential, + session->iframe.headbuf, + sizeof(session->iframe.headbuf), + session->iframe.buf, + session->iframe.len); + if(r == 0) { + r = spdylay_session_on_credential_received(session, &frame); + spdylay_frame_credential_free(&frame.credential); + } else if(spdylay_is_non_fatal(r)) { + r = spdylay_session_fail_session(session, SPDYLAY_GOAWAY_PROTOCOL_ERROR); + } + break; } if(spdylay_is_fatal(r)) { return r; @@ -2360,3 +2588,38 @@ size_t spdylay_session_get_outbound_queue_size(spdylay_session *session) { return spdylay_pq_size(&session->ob_pq)+spdylay_pq_size(&session->ob_ss_pq); } + +int spdylay_session_set_initial_client_cert_origin(spdylay_session *session, + const char *scheme, + const char *host, + uint16_t port) +{ + spdylay_origin *origin; + size_t slot; + if(strlen(scheme) > SPDYLAY_MAX_SCHEME || + strlen(host) > SPDYLAY_MAX_HOSTNAME) { + return SPDYLAY_ERR_INVALID_ARGUMENT; + } + if(session->server || + (session->cli_certvec.size == 0 || + session->cli_certvec.last_slot != 0)) { + return SPDYLAY_ERR_INVALID_STATE; + } + origin = malloc(sizeof(spdylay_origin)); + if(origin == NULL) { + return SPDYLAY_ERR_NOMEM; + } + strcpy(origin->scheme, scheme); + strcpy(origin->host, host); + origin->port = port; + slot = spdylay_client_cert_vector_put(&session->cli_certvec, origin); + assert(slot == 1); + return 0; +} + +const spdylay_origin* spdylay_session_get_client_cert_origin +(spdylay_session *session, + size_t slot) +{ + return spdylay_client_cert_vector_get_origin(&session->cli_certvec, slot); +} diff --git a/lib/spdylay_session.h b/lib/spdylay_session.h index f3f280c7..c2fa079c 100644 --- a/lib/spdylay_session.h +++ b/lib/spdylay_session.h @@ -37,6 +37,7 @@ #include "spdylay_stream.h" #include "spdylay_buffer.h" #include "spdylay_outbound_item.h" +#include "spdylay_client_cert_vector.h" /** * @macro @@ -74,6 +75,11 @@ typedef struct { #define SPDYLAY_INITIAL_WINDOW_SIZE 65536 +/* Initial size of client certificate vector */ +#define SPDYLAY_INITIAL_CLIENT_CERT_VECTOR_LENGTH 8 +/* Maxmum size of client certificate vector */ +#define SPDYLAY_MAX_CLIENT_CERT_VECTOR_LENGTH 255 + typedef enum { SPDYLAY_RECV_HEAD, SPDYLAY_RECV_PAYLOAD @@ -167,6 +173,9 @@ struct spdylay_session { /* Settings value of the local endpoint. */ uint32_t local_settings[SPDYLAY_SETTINGS_MAX+1]; + /* Client certificate vector */ + spdylay_client_cert_vector cli_certvec; + spdylay_session_callbacks callbacks; void *user_data; }; @@ -411,6 +420,15 @@ int spdylay_session_on_headers_received(spdylay_session *session, int spdylay_session_on_window_update_received(spdylay_session *session, spdylay_frame *frame); +/* + * Called when CREDENTIAL is received, assuming |frame.credential| is + * properly initialized. + * + * Currently, this function always succeeds and returns 0. + */ +int spdylay_session_on_credential_received(spdylay_session *session, + spdylay_frame *frame); + /* * Called when DATA is received. * @@ -495,4 +513,21 @@ void spdylay_session_update_local_settings(spdylay_session *session, spdylay_settings_entry *iv, size_t niv); +/* + * Returns the index in the client certificate vector for the + * |syn_stream|. The origin is computed from |syn_stream->nv|. If no + * client certificate is required, return 0. If CREDENTIAL frame needs + * to be sent before the |syn_stream|, this function returns + * :macro:`SPDYLAY_ERR_CREDENTIAL_PENDING`. In this case, CREDENTIAL + * frame has been already queued. This function returns one of the + * following negative error codes: + * + * SPDYLAY_ERR_NOMEM + * Out of memory. + * SPDYLAY_ERR_CREDENTIAL_PENDING + * The CREDENTIAL frame must be sent before the |syn_stream|. + */ +int spdylay_session_prep_credential(spdylay_session *session, + spdylay_syn_stream *syn_stream); + #endif /* SPDYLAY_SESSION_H */ diff --git a/tests/Makefile.am b/tests/Makefile.am index 03e19045..403939b0 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -27,11 +27,13 @@ check_PROGRAMS = main OBJECTS = main.c spdylay_pq_test.c spdylay_map_test.c spdylay_queue_test.c \ spdylay_buffer_test.c spdylay_zlib_test.c spdylay_session_test.c \ - spdylay_frame_test.c spdylay_stream_test.c spdylay_npn_test.c + spdylay_frame_test.c spdylay_stream_test.c spdylay_npn_test.c \ + spdylay_client_cert_vector_test.c HFILES = spdylay_pq_test.h spdylay_map_test.h spdylay_queue_test.h \ spdylay_buffer_test.h spdylay_zlib_test.h spdylay_session_test.h \ - spdylay_frame_test.h spdylay_stream_test.h spdylay_npn_test.h + spdylay_frame_test.h spdylay_stream_test.h spdylay_npn_test.h \ + spdylay_client_cert_vector_test.h main_SOURCES = $(HFILES) $(OBJECTS) diff --git a/tests/main.c b/tests/main.c index 2162192c..453e77ad 100644 --- a/tests/main.c +++ b/tests/main.c @@ -35,6 +35,7 @@ #include "spdylay_frame_test.h" #include "spdylay_stream_test.h" #include "spdylay_npn_test.h" +#include "spdylay_client_cert_vector_test.h" static int init_suite1(void) { @@ -144,6 +145,12 @@ int main(int argc, char* argv[]) test_spdylay_submit_settings) || !CU_add_test(pSuite, "session_get_outbound_queue_size", test_spdylay_session_get_outbound_queue_size) || + !CU_add_test(pSuite, "session_prep_credential", + test_spdylay_session_prep_credential) || + !CU_add_test(pSuite, "session_submit_syn_stream_with_credential", + test_spdylay_submit_syn_stream_with_credential) || + !CU_add_test(pSuite, "session_set_initial_client_cert_origin", + test_spdylay_session_set_initial_client_cert_origin) || !CU_add_test(pSuite, "frame_unpack_nv_spdy2", test_spdylay_frame_unpack_nv_spdy2) || !CU_add_test(pSuite, "frame_unpack_nv_spdy3", @@ -175,6 +182,8 @@ int main(int argc, char* argv[]) test_spdylay_frame_pack_settings_spdy2) || !CU_add_test(pSuite, "frame_pack_settings_spdy3", test_spdylay_frame_pack_settings_spdy3) || + !CU_add_test(pSuite, "frame_pack_credential", + test_spdylay_frame_pack_credential) || !CU_add_test(pSuite, "frame_nv_sort", test_spdylay_frame_nv_sort) || !CU_add_test(pSuite, "frame_nv_downcase", test_spdylay_frame_nv_downcase) || @@ -186,8 +195,16 @@ int main(int argc, char* argv[]) test_spdylay_frame_unpack_nv_check_name_spdy2) || !CU_add_test(pSuite, "frame_unpack_nv_check_name_spdy3", test_spdylay_frame_unpack_nv_check_name_spdy3) || + !CU_add_test(pSuite, "frame_nv_set_origin", + test_spdylay_frame_nv_set_origin) || !CU_add_test(pSuite, "stream_add_pushed_stream", - test_spdylay_stream_add_pushed_stream)) { + test_spdylay_stream_add_pushed_stream) || + !CU_add_test(pSuite, "client_cert_vector_find", + test_spdylay_client_cert_vector_find) || + !CU_add_test(pSuite, "client_cert_vector_resize", + test_spdylay_client_cert_vector_resize) || + !CU_add_test(pSuite, "client_cert_vector_get_origin", + test_spdylay_client_cert_vector_get_origin)) { CU_cleanup_registry(); return CU_get_error(); } diff --git a/tests/spdylay_client_cert_vector_test.c b/tests/spdylay_client_cert_vector_test.c new file mode 100644 index 00000000..236d9207 --- /dev/null +++ b/tests/spdylay_client_cert_vector_test.c @@ -0,0 +1,124 @@ +/* + * Spdylay - SPDY Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "spdylay_client_cert_vector_test.h" + +#include + +#include "spdylay_client_cert_vector.h" + +static spdylay_origin* create_origin(const char *scheme, const char *host, + uint16_t port) +{ + spdylay_origin *origin = malloc(sizeof(spdylay_origin)); + spdylay_origin_set(origin, scheme, host, port); + return origin; +} + +void test_spdylay_client_cert_vector_find(void) +{ + spdylay_client_cert_vector certvec; + spdylay_origin *origin; + const spdylay_origin *origin_get; + size_t slot; + spdylay_client_cert_vector_init(&certvec, 3); + + origin = create_origin("https", "example.org", 443); + CU_ASSERT(0 == spdylay_client_cert_vector_find(&certvec, origin)); + CU_ASSERT(1 == spdylay_client_cert_vector_put(&certvec, origin)); + slot = spdylay_client_cert_vector_find(&certvec, origin); + CU_ASSERT(1 == slot); + origin_get = spdylay_client_cert_vector_get_origin(&certvec, slot); + CU_ASSERT(strcmp(origin->scheme, origin_get->scheme) == 0); + CU_ASSERT(strcmp(origin->host, origin_get->host) == 0); + CU_ASSERT(origin->port == origin_get->port); + + origin = create_origin("https", "example.org", 8443); + CU_ASSERT(0 == spdylay_client_cert_vector_find(&certvec, origin)); + CU_ASSERT(2 == spdylay_client_cert_vector_put(&certvec, origin)); + slot = spdylay_client_cert_vector_find(&certvec, origin); + CU_ASSERT(2 == slot); + + origin = create_origin("https", "example.com", 443); + CU_ASSERT(0 == spdylay_client_cert_vector_find(&certvec, origin)); + CU_ASSERT(3 == spdylay_client_cert_vector_put(&certvec, origin)); + slot = spdylay_client_cert_vector_find(&certvec, origin); + CU_ASSERT(3 == slot); + + origin = create_origin("https", "example.com", 8443); + CU_ASSERT(0 == spdylay_client_cert_vector_find(&certvec, origin)); + CU_ASSERT(1 == spdylay_client_cert_vector_put(&certvec, origin)); + slot = spdylay_client_cert_vector_find(&certvec, origin); + CU_ASSERT(1 == slot); + + origin = create_origin("https", "example.org", 443); + CU_ASSERT(0 == spdylay_client_cert_vector_find(&certvec, origin)); + free(origin); + + spdylay_client_cert_vector_free(&certvec); +} + +void test_spdylay_client_cert_vector_resize(void) +{ + spdylay_client_cert_vector certvec; + spdylay_origin *origin; + size_t i; + spdylay_client_cert_vector_init(&certvec, 3); + + origin = create_origin("https", "example.org", 443); + spdylay_client_cert_vector_put(&certvec, origin); + origin = create_origin("https", "example.com", 443); + spdylay_client_cert_vector_put(&certvec, origin); + + CU_ASSERT(0 == spdylay_client_cert_vector_resize(&certvec, 1)); + CU_ASSERT(NULL != spdylay_client_cert_vector_get_origin(&certvec, 1)); + CU_ASSERT(1 == certvec.last_slot); + + CU_ASSERT(0 == spdylay_client_cert_vector_resize(&certvec, 8)); + CU_ASSERT(NULL != spdylay_client_cert_vector_get_origin(&certvec, 1)); + CU_ASSERT(1 == certvec.last_slot); + for(i = 2; i <= 8; ++i) { + CU_ASSERT(NULL == spdylay_client_cert_vector_get_origin(&certvec, i)); + } + + spdylay_client_cert_vector_free(&certvec); +} + +void test_spdylay_client_cert_vector_get_origin(void) +{ + spdylay_client_cert_vector certvec; + spdylay_origin *origin; + spdylay_client_cert_vector_init(&certvec, 3); + + origin = create_origin("https", "example.org", 443); + CU_ASSERT(1 == spdylay_client_cert_vector_put(&certvec, origin)); + + CU_ASSERT(NULL == spdylay_client_cert_vector_get_origin(&certvec, 0)); + CU_ASSERT(NULL != spdylay_client_cert_vector_get_origin(&certvec, 1)); + CU_ASSERT(NULL == spdylay_client_cert_vector_get_origin(&certvec, 2)); + CU_ASSERT(NULL == spdylay_client_cert_vector_get_origin(&certvec, 3)); + CU_ASSERT(NULL == spdylay_client_cert_vector_get_origin(&certvec, 4)); + + spdylay_client_cert_vector_free(&certvec); +} diff --git a/tests/spdylay_client_cert_vector_test.h b/tests/spdylay_client_cert_vector_test.h new file mode 100644 index 00000000..553b4202 --- /dev/null +++ b/tests/spdylay_client_cert_vector_test.h @@ -0,0 +1,32 @@ +/* + * Spdylay - SPDY Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SPDYLAY_CLIENT_CERT_VECTOR_TEST_H +#define SPDYLAY_CLIENT_CERT_VECTOR_TEST_H + +void test_spdylay_client_cert_vector_find(void); +void test_spdylay_client_cert_vector_resize(void); +void test_spdylay_client_cert_vector_get_origin(void); + +#endif /* SPDYLAY_CLIENT_CERT_VECTOR_TEST_H */ diff --git a/tests/spdylay_frame_test.c b/tests/spdylay_frame_test.c index 06224397..87b143b0 100644 --- a/tests/spdylay_frame_test.c +++ b/tests/spdylay_frame_test.c @@ -517,6 +517,66 @@ void test_spdylay_frame_pack_settings_spdy3(void) test_spdylay_frame_pack_settings_version(SPDYLAY_PROTO_SPDY3); } +static char* strcopy(const char* s) +{ + size_t len = strlen(s); + char *dest = malloc(len+1); + memcpy(dest, s, len); + dest[len] = '\0'; + return dest; +} + +void test_spdylay_frame_pack_credential(void) +{ + spdylay_frame frame, oframe; + uint8_t *buf = NULL; + size_t buflen = 0; + ssize_t framelen; + spdylay_mem_chunk proof; + spdylay_mem_chunk *certs; + size_t ncerts; + proof.data = (uint8_t*)strcopy("PROOF"); + proof.length = strlen("PROOF"); + ncerts = 2; + certs = malloc(sizeof(spdylay_mem_chunk)*ncerts); + certs[0].data = (uint8_t*)strcopy("CERT0"); + certs[0].length = strlen("CERT0"); + certs[1].data = (uint8_t*)strcopy("CERT1"); + certs[1].length = strlen("CERT1"); + spdylay_frame_credential_init(&frame.credential, SPDYLAY_PROTO_SPDY3, + 1, &proof, certs, ncerts); + framelen = spdylay_frame_pack_credential(&buf, &buflen, &frame.credential); + CU_ASSERT(0 == spdylay_frame_unpack_credential + (&oframe.credential, + &buf[0], SPDYLAY_FRAME_HEAD_LENGTH, + &buf[SPDYLAY_FRAME_HEAD_LENGTH], + framelen-SPDYLAY_FRAME_HEAD_LENGTH)); + CU_ASSERT(1 == oframe.credential.slot); + CU_ASSERT(5 == oframe.credential.proof.length); + CU_ASSERT(memcmp("PROOF", oframe.credential.proof.data, 5) == 0); + CU_ASSERT(2 == oframe.credential.ncerts); + CU_ASSERT(5 == oframe.credential.certs[0].length); + CU_ASSERT(memcmp("CERT0", oframe.credential.certs[0].data, 5) == 0); + CU_ASSERT(5 == oframe.credential.certs[1].length); + CU_ASSERT(memcmp("CERT1", oframe.credential.certs[1].data, 5) == 0); + CU_ASSERT(SPDYLAY_PROTO_SPDY3 == oframe.credential.hd.version); + CU_ASSERT(SPDYLAY_CREDENTIAL == oframe.credential.hd.type); + CU_ASSERT(SPDYLAY_CTRL_FLAG_NONE == oframe.credential.hd.flags); + CU_ASSERT(framelen-SPDYLAY_FRAME_HEAD_LENGTH == oframe.credential.hd.length); + spdylay_frame_credential_free(&oframe.credential); + + /* Put large certificate length */ + spdylay_put_uint32be(&buf[8+2+4+5], INT32_MAX); + CU_ASSERT(SPDYLAY_ERR_INVALID_FRAME == spdylay_frame_unpack_credential + (&oframe.credential, + &buf[0], SPDYLAY_FRAME_HEAD_LENGTH, + &buf[SPDYLAY_FRAME_HEAD_LENGTH], + framelen-SPDYLAY_FRAME_HEAD_LENGTH)); + + free(buf); + spdylay_frame_credential_free(&frame.credential); +} + void test_spdylay_frame_nv_sort(void) { char *nv[7]; @@ -670,3 +730,49 @@ void test_spdylay_frame_unpack_nv_check_name_spdy3(void) test_spdylay_frame_unpack_nv_check_name_with (spdylay_frame_get_len_size(SPDYLAY_PROTO_SPDY3)); } + +void test_spdylay_frame_nv_set_origin(void) +{ + spdylay_origin origin; + const char *nv1[] = { + ":host", "example.org", + ":scheme", "https", + NULL + }; + const char *nv2[] = { + ":host", "example.org:8443", + ":scheme", "https", + NULL + }; + const char *nv3[] = { + ":host", "example.org:0", + ":scheme", "https", + NULL + }; + const char *nv4[] = { + ":host", "example.org", + NULL + }; + const char *nv5[] = { + ":scheme", "https", + NULL + }; + CU_ASSERT(0 == spdylay_frame_nv_set_origin((char**)nv1, &origin)); + CU_ASSERT(strcmp("https", origin.scheme) == 0); + CU_ASSERT(strcmp("example.org", origin.host) == 0); + CU_ASSERT(443 == origin.port); + + CU_ASSERT(0 == spdylay_frame_nv_set_origin((char**)nv2, &origin)); + CU_ASSERT(strcmp("https", origin.scheme) == 0); + CU_ASSERT(strcmp("example.org", origin.host) == 0); + CU_ASSERT(8443 == origin.port); + + CU_ASSERT(SPDYLAY_ERR_INVALID_ARGUMENT == + spdylay_frame_nv_set_origin((char**)nv3, &origin)); + + CU_ASSERT(SPDYLAY_ERR_INVALID_ARGUMENT == + spdylay_frame_nv_set_origin((char**)nv4, &origin)); + + CU_ASSERT(SPDYLAY_ERR_INVALID_ARGUMENT == + spdylay_frame_nv_set_origin((char**)nv5, &origin)); +} diff --git a/tests/spdylay_frame_test.h b/tests/spdylay_frame_test.h index 10a402a4..7a513da3 100644 --- a/tests/spdylay_frame_test.h +++ b/tests/spdylay_frame_test.h @@ -42,11 +42,13 @@ void test_spdylay_frame_pack_headers_spdy3(void); void test_spdylay_frame_pack_window_update(void); void test_spdylay_frame_pack_settings_spdy2(void); void test_spdylay_frame_pack_settings_spdy3(void); +void test_spdylay_frame_pack_credential(void); void test_spdylay_frame_nv_sort(void); void test_spdylay_frame_nv_downcase(void); void test_spdylay_frame_nv_2to3(void); void test_spdylay_frame_nv_3to2(void); void test_spdylay_frame_unpack_nv_check_name_spdy2(void); void test_spdylay_frame_unpack_nv_check_name_spdy3(void); +void test_spdylay_frame_nv_set_origin(void); #endif /* SPDYLAY_FRAME_TEST_H */ diff --git a/tests/spdylay_session_test.c b/tests/spdylay_session_test.c index 228b9150..355dabd7 100644 --- a/tests/spdylay_session_test.c +++ b/tests/spdylay_session_test.c @@ -1924,7 +1924,7 @@ void test_spdylay_session_on_settings_received(void) iv[2].value = 64*1024; iv[2].flags = SPDYLAY_ID_FLAG_SETTINGS_NONE; - iv[3].settings_id = SPDYLAY_SETTINGS_CURRENT_CWND; + iv[3].settings_id = SPDYLAY_SETTINGS_CLIENT_CERTIFICATE_VECTOR_SIZE; iv[3].value = 512; iv[3].flags = SPDYLAY_ID_FLAG_SETTINGS_NONE; @@ -1933,7 +1933,7 @@ void test_spdylay_session_on_settings_received(void) iv[4].flags = SPDYLAY_ID_FLAG_SETTINGS_NONE; memset(&callbacks, 0, sizeof(spdylay_session_callbacks)); - spdylay_session_server_new(&session, SPDYLAY_PROTO_SPDY3, &callbacks, + spdylay_session_client_new(&session, SPDYLAY_PROTO_SPDY3, &callbacks, &user_data); session->remote_settings[SPDYLAY_SETTINGS_INITIAL_WINDOW_SIZE] = 16*1024; @@ -1953,8 +1953,11 @@ void test_spdylay_session_on_settings_received(void) session->remote_settings[SPDYLAY_SETTINGS_MAX_CONCURRENT_STREAMS]); CU_ASSERT(64*1024 == session->remote_settings[SPDYLAY_SETTINGS_INITIAL_WINDOW_SIZE]); - CU_ASSERT(512 == - session->remote_settings[SPDYLAY_SETTINGS_CURRENT_CWND]); + /* We limit certificate vector in reasonable size. */ + CU_ASSERT(SPDYLAY_MAX_CLIENT_CERT_VECTOR_LENGTH == + session->remote_settings + [SPDYLAY_SETTINGS_CLIENT_CERTIFICATE_VECTOR_SIZE]); + CU_ASSERT(SPDYLAY_MAX_CLIENT_CERT_VECTOR_LENGTH == session->cli_certvec.size); CU_ASSERT(64*1024 == stream1->window_size); CU_ASSERT(0 == stream2->window_size); @@ -2064,3 +2067,157 @@ void test_spdylay_session_get_outbound_queue_size(void) spdylay_session_del(session); } + +static ssize_t get_credential_ncerts(spdylay_session *session, + const spdylay_origin *origin, + void *user_data) +{ + if(strcmp("example.org", origin->host) == 0 && + strcmp("https", origin->scheme) == 0 && + 443 == origin->port) { + return 2; + } else { + return 0; + } +} + +static ssize_t get_credential_cert(spdylay_session *session, + const spdylay_origin *origin, + size_t index, + uint8_t *cert, size_t certlen, + void *user_data) +{ + size_t len = strlen(origin->host); + if(certlen == 0) { + return len; + } else { + assert(certlen == len); + memcpy(cert, origin->host, len); + return 0; + } +} + +static ssize_t get_credential_proof(spdylay_session *session, + const spdylay_origin *origin, + uint8_t *proof, size_t prooflen, + void *uer_data) +{ + size_t len = strlen(origin->scheme); + if(prooflen == 0) { + return len; + } else { + assert(prooflen == len); + memcpy(proof, origin->scheme, len); + return 0; + } +} + +void test_spdylay_session_prep_credential(void) +{ + spdylay_session *session; + spdylay_session_callbacks callbacks; + const char *nv[] = { ":host", "example.org", + ":scheme", "https", + NULL }; + const char *nv_nocert[] = { ":host", "nocert", + ":scheme", "https", + NULL }; + spdylay_frame frame, *cred_frame; + spdylay_outbound_item *item; + size_t i; + memset(&callbacks, 0, sizeof(spdylay_session_callbacks)); + callbacks.get_credential_ncerts = get_credential_ncerts; + callbacks.get_credential_cert = get_credential_cert; + callbacks.get_credential_proof = get_credential_proof; + CU_ASSERT(0 == spdylay_session_client_new(&session, SPDYLAY_PROTO_SPDY3, + &callbacks, NULL)); + spdylay_frame_syn_stream_init(&frame.syn_stream, session->version, + SPDYLAY_CTRL_FLAG_NONE, 1, 0, 0, + dup_nv(nv)); + CU_ASSERT(SPDYLAY_ERR_CREDENTIAL_PENDING == + spdylay_session_prep_credential(session, &frame.syn_stream)); + item = spdylay_session_get_next_ob_item(session); + CU_ASSERT(SPDYLAY_CREDENTIAL == OB_CTRL_TYPE(item)); + CU_ASSERT(SPDYLAY_OB_PRI_CREDENTIAL == item->pri); + cred_frame = OB_CTRL(item); + CU_ASSERT(strlen("https") == cred_frame->credential.proof.length); + CU_ASSERT(memcmp("https", cred_frame->credential.proof.data, + cred_frame->credential.proof.length) == 0); + CU_ASSERT(2 == cred_frame->credential.ncerts); + for(i = 0; i < cred_frame->credential.ncerts; ++i) { + CU_ASSERT(strlen("example.org") == cred_frame->credential.certs[i].length); + CU_ASSERT(memcmp("example.org", cred_frame->credential.certs[i].data, + cred_frame->credential.certs[i].length) == 0); + } + /* Next spdylay_session_get_next_ob_item() call returns slot index */ + CU_ASSERT(1 == spdylay_session_prep_credential(session, &frame.syn_stream)); + + spdylay_frame_syn_stream_free(&frame.syn_stream); + + spdylay_frame_syn_stream_init(&frame.syn_stream, session->version, + SPDYLAY_CTRL_FLAG_NONE, 1, 0, 0, + dup_nv(nv_nocert)); + CU_ASSERT(0 == spdylay_session_prep_credential(session, &frame.syn_stream)); + spdylay_frame_syn_stream_free(&frame.syn_stream); + + spdylay_session_del(session); +} + +void test_spdylay_submit_syn_stream_with_credential(void) +{ + spdylay_session *session; + spdylay_session_callbacks callbacks; + const char *nv[] = { ":host", "example.org", + ":scheme", "https", + NULL }; + my_user_data ud; + accumulator acc; + + ud.acc = &acc; + memset(&callbacks, 0, sizeof(spdylay_session_callbacks)); + callbacks.send_callback = block_count_send_callback; + callbacks.on_ctrl_send_callback = on_ctrl_send_callback; + callbacks.get_credential_ncerts = get_credential_ncerts; + callbacks.get_credential_cert = get_credential_cert; + callbacks.get_credential_proof = get_credential_proof; + + CU_ASSERT(0 == spdylay_session_client_new(&session, SPDYLAY_PROTO_SPDY3, + &callbacks, &ud)); + + CU_ASSERT(0 == spdylay_submit_request(session, 0, nv, NULL, NULL)); + + ud.block_count = 1; + ud.ctrl_send_cb_called = 0; + CU_ASSERT(0 == spdylay_session_send(session)); + + CU_ASSERT(1 == ud.ctrl_send_cb_called); + CU_ASSERT(SPDYLAY_CREDENTIAL == ud.sent_frame_type); + + session->callbacks.send_callback = accumulator_send_callback; + acc.length = 0; + ud.ctrl_send_cb_called = 0; + CU_ASSERT(0 == spdylay_session_send(session)); + CU_ASSERT(1 == ud.ctrl_send_cb_called); + CU_ASSERT(SPDYLAY_SYN_STREAM == ud.sent_frame_type); + /* Check slot */ + CU_ASSERT(1 == acc.buf[17]); + + spdylay_session_del(session); +} + +void test_spdylay_session_set_initial_client_cert_origin(void) +{ + spdylay_session *session; + spdylay_session_callbacks callbacks; + const spdylay_origin *origin; + spdylay_session_client_new(&session, SPDYLAY_PROTO_SPDY3, &callbacks, NULL); + CU_ASSERT(0 == spdylay_session_set_initial_client_cert_origin + (session, "https", "example.org", 443)); + origin = spdylay_session_get_client_cert_origin(session, 1); + CU_ASSERT(NULL != origin); + CU_ASSERT(strcmp("https", spdylay_origin_get_scheme(origin)) == 0); + CU_ASSERT(strcmp("example.org", spdylay_origin_get_host(origin)) == 0); + CU_ASSERT(443 == spdylay_origin_get_port(origin)); + + spdylay_session_del(session); +} diff --git a/tests/spdylay_session_test.h b/tests/spdylay_session_test.h index 8deeee98..ea1ff770 100644 --- a/tests/spdylay_session_test.h +++ b/tests/spdylay_session_test.h @@ -64,5 +64,8 @@ void test_spdylay_session_on_ctrl_not_send(void); void test_spdylay_session_on_settings_received(void); void test_spdylay_submit_settings(void); void test_spdylay_session_get_outbound_queue_size(void); +void test_spdylay_session_prep_credential(void); +void test_spdylay_submit_syn_stream_with_credential(void); +void test_spdylay_session_set_initial_client_cert_origin(void); #endif /* SPDYLAY_SESSION_TEST_H */