+ if (!payload->pkcs_alg_len && !payload->pkcs_alg_list) {
+ SILC_LOG_DEBUG(("Could not find supported PKCS alg"));
+ silc_free(payload->ke_grp_list);
+ silc_free(payload);
+ return SILC_SKE_STATUS_UNKNOWN_PKCS;
+ }
+ } else {
+ SILC_LOG_DEBUG(("Proposed PKCS alg `%s'", rp->pkcs_alg_list));
+ SILC_LOG_DEBUG(("Found PKCS alg `%s'", rp->pkcs_alg_list));
+
+ payload->pkcs_alg_len = rp->pkcs_alg_len;
+ payload->pkcs_alg_list = strdup(rp->pkcs_alg_list);
+ }
+
+ /* Get supported encryption algorithms */
+ cp = rp->enc_alg_list;
+ if (cp && strchr(cp, ',')) {
+ while(cp) {
+ char *item;
+
+ len = strcspn(cp, ",");
+ item = silc_calloc(len + 1, sizeof(char));
+ if (!item) {
+ ske->status = SILC_SKE_STATUS_OUT_OF_MEMORY;
+ return status;
+ }
+ memcpy(item, cp, len);
+
+ SILC_LOG_DEBUG(("Proposed encryption alg `%s'", item));
+
+ if (silc_cipher_is_supported(item) == TRUE) {
+ SILC_LOG_DEBUG(("Found encryption alg `%s'", item));
+
+ payload->enc_alg_len = len;
+ payload->enc_alg_list = item;
+ break;
+ }
+
+ cp += len;
+ if (strlen(cp) == 0)
+ cp = NULL;
+ else
+ cp++;
+
+ if (item)
+ silc_free(item);
+ }
+
+ if (!payload->enc_alg_len && !payload->enc_alg_list) {
+ SILC_LOG_DEBUG(("Could not find supported encryption alg"));
+ silc_free(payload->ke_grp_list);
+ silc_free(payload->pkcs_alg_list);
+ silc_free(payload);
+ return SILC_SKE_STATUS_UNKNOWN_CIPHER;
+ }
+ } else {
+ SILC_LOG_DEBUG(("Proposed encryption alg `%s' and selected it",
+ rp->enc_alg_list));
+
+ payload->enc_alg_len = rp->enc_alg_len;
+ payload->enc_alg_list = strdup(rp->enc_alg_list);
+ }
+
+ /* Save selected cipher to security properties */
+ if (silc_cipher_alloc(payload->enc_alg_list, &(*prop)->cipher) == FALSE) {
+ silc_free(payload->ke_grp_list);
+ silc_free(payload->pkcs_alg_list);
+ silc_free(payload);
+ return SILC_SKE_STATUS_UNKNOWN_CIPHER;
+ }
+
+ /* Get supported hash algorithms */
+ cp = rp->hash_alg_list;
+ if (cp && strchr(cp, ',')) {
+ while(cp) {
+ char *item;
+
+ len = strcspn(cp, ",");
+ item = silc_calloc(len + 1, sizeof(char));
+ if (!item) {
+ ske->status = SILC_SKE_STATUS_OUT_OF_MEMORY;
+ return status;
+ }
+ memcpy(item, cp, len);
+
+ SILC_LOG_DEBUG(("Proposed hash alg `%s'", item));
+
+ if (silc_hash_is_supported(item) == TRUE) {
+ SILC_LOG_DEBUG(("Found hash alg `%s'", item));
+
+ payload->hash_alg_len = len;
+ payload->hash_alg_list = item;
+ break;
+ }
+
+ cp += len;
+ if (strlen(cp) == 0)
+ cp = NULL;
+ else
+ cp++;
+
+ if (item)
+ silc_free(item);
+ }
+
+ if (!payload->hash_alg_len && !payload->hash_alg_list) {
+ SILC_LOG_DEBUG(("Could not find supported hash alg"));
+ silc_free(payload->ke_grp_list);
+ silc_free(payload->pkcs_alg_list);
+ silc_free(payload->enc_alg_list);
+ silc_free(payload);
+ return SILC_SKE_STATUS_UNKNOWN_HASH_FUNCTION;
+ }
+ } else {
+ SILC_LOG_DEBUG(("Proposed hash alg `%s' and selected it",
+ rp->hash_alg_list));
+
+ payload->hash_alg_len = rp->hash_alg_len;
+ payload->hash_alg_list = strdup(rp->hash_alg_list);
+ }
+
+ /* Save selected hash algorithm to security properties */
+ if (silc_hash_alloc(payload->hash_alg_list, &(*prop)->hash) == FALSE) {
+ silc_free(payload->ke_grp_list);
+ silc_free(payload->pkcs_alg_list);
+ silc_free(payload->enc_alg_list);
+ silc_free(payload);
+ return SILC_SKE_STATUS_UNKNOWN_HASH_FUNCTION;
+ }
+
+ /* Get supported HMACs */
+ cp = rp->hmac_alg_list;
+ if (cp && strchr(cp, ',')) {
+ while(cp) {
+ char *item;
+
+ len = strcspn(cp, ",");
+ item = silc_calloc(len + 1, sizeof(char));
+ if (!item) {
+ ske->status = SILC_SKE_STATUS_OUT_OF_MEMORY;
+ return status;
+ }
+ memcpy(item, cp, len);
+
+ SILC_LOG_DEBUG(("Proposed HMAC `%s'", item));
+
+ if (silc_hmac_is_supported(item) == TRUE) {
+ SILC_LOG_DEBUG(("Found HMAC `%s'", item));
+
+ payload->hmac_alg_len = len;
+ payload->hmac_alg_list = item;
+ break;
+ }
+
+ cp += len;
+ if (strlen(cp) == 0)
+ cp = NULL;
+ else
+ cp++;
+
+ if (item)
+ silc_free(item);
+ }
+
+ if (!payload->hmac_alg_len && !payload->hmac_alg_list) {
+ SILC_LOG_DEBUG(("Could not find supported HMAC"));
+ silc_free(payload->ke_grp_list);
+ silc_free(payload->pkcs_alg_list);
+ silc_free(payload->enc_alg_list);
+ silc_free(payload->hash_alg_list);
+ silc_free(payload);
+ return SILC_SKE_STATUS_UNKNOWN_HMAC;
+ }
+ } else {
+ SILC_LOG_DEBUG(("Proposed HMAC `%s' and selected it",
+ rp->hmac_alg_list));
+
+ payload->hmac_alg_len = rp->hmac_alg_len;
+ payload->hmac_alg_list = strdup(rp->hmac_alg_list);
+ }
+
+ /* Save selected HMACc to security properties */
+ if (silc_hmac_alloc(payload->hmac_alg_list, NULL, &(*prop)->hmac) == FALSE) {
+ silc_free(payload->ke_grp_list);
+ silc_free(payload->pkcs_alg_list);
+ silc_free(payload->enc_alg_list);
+ silc_free(payload->hash_alg_list);
+ silc_free(payload);
+ return SILC_SKE_STATUS_UNKNOWN_HMAC;
+ }
+
+ /* Get supported compression algorithms */
+ cp = rp->comp_alg_list;
+ if (cp && strchr(cp, ',')) {
+ while(cp) {
+ char *item;
+
+ len = strcspn(cp, ",");
+ item = silc_calloc(len + 1, sizeof(char));
+ if (!item) {
+ ske->status = SILC_SKE_STATUS_OUT_OF_MEMORY;
+ return status;
+ }
+ memcpy(item, cp, len);
+
+ SILC_LOG_DEBUG(("Proposed Compression `%s'", item));
+
+#if 1
+ if (!strcmp(item, "none")) {
+ SILC_LOG_DEBUG(("Found Compression `%s'", item));
+ payload->comp_alg_len = len;
+ payload->comp_alg_list = item;
+ break;
+ }
+#else
+ if (silc_hmac_is_supported(item) == TRUE) {
+ SILC_LOG_DEBUG(("Found Compression `%s'", item));
+ payload->comp_alg_len = len;
+ payload->comp_alg_list = item;
+ break;
+ }
+#endif
+
+ cp += len;
+ if (strlen(cp) == 0)
+ cp = NULL;
+ else
+ cp++;
+
+ if (item)
+ silc_free(item);
+ }
+ }
+
+ payload->len = 1 + 1 + 2 + SILC_SKE_COOKIE_LEN +
+ 2 + payload->version_len +
+ 2 + payload->ke_grp_len + 2 + payload->pkcs_alg_len +
+ 2 + payload->enc_alg_len + 2 + payload->hash_alg_len +
+ 2 + payload->hmac_alg_len + 2 + payload->comp_alg_len;
+
+ /* Save our reply payload */
+ ske->start_payload = payload;
+
+ return SILC_SKE_STATUS_OK;
+}
+
+/* Creates random number such that 1 < rnd < n and at most length
+ of len bits. The rnd sent as argument must be initialized. */
+
+static SilcSKEStatus silc_ske_create_rnd(SilcSKE ske, SilcMPInt *n,
+ SilcUInt32 len,
+ SilcMPInt *rnd)
+{
+ SilcSKEStatus status = SILC_SKE_STATUS_OK;
+ unsigned char *string;
+ SilcUInt32 l;
+
+ if (!len)
+ return SILC_SKE_STATUS_ERROR;
+
+ SILC_LOG_DEBUG(("Creating random number"));
+
+ l = ((len - 1) / 8);
+
+ /* Get the random number as string */
+ string = silc_rng_get_rn_data(ske->rng, l);
+ if (!string)
+ return SILC_SKE_STATUS_OUT_OF_MEMORY;
+
+ /* Decode the string into a MP integer */
+ silc_mp_bin2mp(string, l, rnd);
+ silc_mp_mod_2exp(rnd, rnd, len);
+
+ /* Checks */
+ if (silc_mp_cmp_ui(rnd, 1) < 0)
+ status = SILC_SKE_STATUS_ERROR;
+ if (silc_mp_cmp(rnd, n) >= 0)
+ status = SILC_SKE_STATUS_ERROR;
+
+ memset(string, 'F', l);
+ silc_free(string);
+
+ return status;
+}
+
+/* Creates a hash value HASH as defined in the SKE protocol. If the
+ `initiator' is TRUE then this function is used to create the HASH_i
+ hash value defined in the protocol. If it is FALSE then this is used
+ to create the HASH value defined by the protocol. */
+
+static SilcSKEStatus silc_ske_make_hash(SilcSKE ske,
+ unsigned char *return_hash,
+ SilcUInt32 *return_hash_len,
+ int initiator)
+{
+ SilcSKEStatus status = SILC_SKE_STATUS_OK;
+ SilcBuffer buf;
+ unsigned char *e, *f, *KEY, *s_data;
+ SilcUInt32 e_len, f_len, KEY_len, s_len;
+ int ret;
+
+ SILC_LOG_DEBUG(("Start"));
+
+ if (initiator == FALSE) {
+ s_data = (ske->start_payload_copy ?
+ silc_buffer_data(ske->start_payload_copy) : NULL);
+ s_len = (ske->start_payload_copy ?
+ silc_buffer_len(ske->start_payload_copy) : 0);
+ e = silc_mp_mp2bin(&ske->ke1_payload->x, 0, &e_len);
+ f = silc_mp_mp2bin(&ske->ke2_payload->x, 0, &f_len);
+ KEY = silc_mp_mp2bin(ske->KEY, 0, &KEY_len);
+
+ /* Format the buffer used to compute the hash value */
+ buf = silc_buffer_alloc_size(s_len +
+ ske->ke2_payload->pk_len +
+ ske->ke1_payload->pk_len +
+ e_len + f_len + KEY_len);
+ if (!buf)
+ return SILC_SKE_STATUS_OUT_OF_MEMORY;
+
+ /* Initiator is not required to send its public key */
+ if (!ske->ke1_payload->pk_data) {
+ ret =
+ silc_buffer_format(buf,
+ SILC_STR_DATA(s_data, s_len),
+ SILC_STR_DATA(ske->ke2_payload->pk_data,
+ ske->ke2_payload->pk_len),
+ SILC_STR_DATA(e, e_len),
+ SILC_STR_DATA(f, f_len),
+ SILC_STR_DATA(KEY, KEY_len),
+ SILC_STR_END);
+ } else {
+ ret =
+ silc_buffer_format(buf,
+ SILC_STR_DATA(s_data, s_len),
+ SILC_STR_DATA(ske->ke2_payload->pk_data,
+ ske->ke2_payload->pk_len),
+ SILC_STR_DATA(ske->ke1_payload->pk_data,
+ ske->ke1_payload->pk_len),
+ SILC_STR_DATA(e, e_len),
+ SILC_STR_DATA(f, f_len),
+ SILC_STR_DATA(KEY, KEY_len),
+ SILC_STR_END);
+ }
+ if (ret == -1) {
+ silc_buffer_free(buf);
+ memset(e, 0, e_len);
+ memset(f, 0, f_len);
+ memset(KEY, 0, KEY_len);
+ silc_free(e);
+ silc_free(f);
+ silc_free(KEY);
+ return SILC_SKE_STATUS_ERROR;
+ }
+
+ memset(e, 0, e_len);
+ memset(f, 0, f_len);
+ memset(KEY, 0, KEY_len);
+ silc_free(e);
+ silc_free(f);
+ silc_free(KEY);
+ } else {
+ s_data = (ske->start_payload_copy ?
+ silc_buffer_data(ske->start_payload_copy) : NULL);
+ s_len = (ske->start_payload_copy ?
+ silc_buffer_len(ske->start_payload_copy) : 0);
+ e = silc_mp_mp2bin(&ske->ke1_payload->x, 0, &e_len);
+
+ buf = silc_buffer_alloc_size(s_len + ske->ke1_payload->pk_len + e_len);
+ if (!buf)
+ return SILC_SKE_STATUS_OUT_OF_MEMORY;
+
+ /* Format the buffer used to compute the hash value */
+ ret =
+ silc_buffer_format(buf,
+ SILC_STR_DATA(s_data, s_len),
+ SILC_STR_DATA(ske->ke1_payload->pk_data,
+ ske->ke1_payload->pk_len),
+ SILC_STR_DATA(e, e_len),
+ SILC_STR_END);
+ if (ret == -1) {
+ silc_buffer_free(buf);
+ memset(e, 0, e_len);
+ silc_free(e);
+ return SILC_SKE_STATUS_ERROR;
+ }
+
+ SILC_LOG_HEXDUMP(("hash buf"), buf->data, silc_buffer_len(buf));
+
+ memset(e, 0, e_len);
+ silc_free(e);
+ }
+
+ /* Make the hash */
+ silc_hash_make(ske->prop->hash, buf->data, silc_buffer_len(buf),
+ return_hash);
+ *return_hash_len = silc_hash_len(ske->prop->hash);
+
+ if (initiator == FALSE) {
+ SILC_LOG_HEXDUMP(("HASH"), return_hash, *return_hash_len);
+ } else {
+ SILC_LOG_HEXDUMP(("HASH_i"), return_hash, *return_hash_len);
+ }
+
+ silc_buffer_free(buf);
+
+ return status;
+}
+
+/* Generate rekey material */
+
+static SilcSKERekeyMaterial
+silc_ske_make_rekey_material(SilcSKE ske, SilcSKEKeyMaterial keymat)
+{
+ SilcSKERekeyMaterial rekey;
+ const char *hash;
+
+ /* Create rekey material */
+ rekey = silc_calloc(1, sizeof(*rekey));
+ if (!rekey)
+ return NULL;
+
+ if (ske->prop) {
+ if (ske->prop->group)
+ rekey->ske_group = silc_ske_group_get_number(ske->prop->group);
+ rekey->pfs = (ske->prop->flags & SILC_SKE_SP_FLAG_PFS ? TRUE : FALSE);
+ hash = silc_hash_get_name(ske->prop->hash);
+ rekey->hash = silc_memdup(hash, strlen(hash));
+ if (!rekey->hash)
+ return NULL;
+ }
+
+ if (rekey->pfs == FALSE) {
+ rekey->send_enc_key = silc_memdup(keymat->send_enc_key,
+ keymat->enc_key_len / 8);
+ if (!rekey->send_enc_key) {
+ silc_free(rekey);
+ return NULL;
+ }
+ rekey->enc_key_len = keymat->enc_key_len;
+ }
+
+ return rekey;
+}
+
+/* Assembles security properties */
+
+static SilcSKEStartPayload
+silc_ske_assemble_security_properties(SilcSKE ske,
+ SilcSKESecurityPropertyFlag flags,
+ const char *version)
+{
+ SilcSKEStartPayload rp;
+ int i;
+
+ SILC_LOG_DEBUG(("Assembling KE Start Payload"));
+
+ rp = silc_calloc(1, sizeof(*rp));
+
+ /* Set flags */
+ rp->flags = (unsigned char)flags;
+
+ /* Set random cookie */
+ rp->cookie = silc_calloc(SILC_SKE_COOKIE_LEN, sizeof(*rp->cookie));
+ for (i = 0; i < SILC_SKE_COOKIE_LEN; i++)
+ rp->cookie[i] = silc_rng_get_byte_fast(ske->rng);
+ rp->cookie_len = SILC_SKE_COOKIE_LEN;
+
+ /* In case IV included flag and session port is set the first 16-bits of
+ cookie will include our session port. */
+ if (flags & SILC_SKE_SP_FLAG_IV_INCLUDED && ske->session_port)
+ SILC_PUT16_MSB(ske->session_port, rp->cookie);
+
+ /* Put version */
+ rp->version = strdup(version);
+ rp->version_len = strlen(version);
+
+ /* Get supported Key Exhange groups */
+ rp->ke_grp_list = silc_ske_get_supported_groups();
+ rp->ke_grp_len = strlen(rp->ke_grp_list);
+
+ /* Get supported PKCS algorithms */
+ rp->pkcs_alg_list = silc_pkcs_get_supported();
+ rp->pkcs_alg_len = strlen(rp->pkcs_alg_list);
+
+ /* Get supported encryption algorithms */
+ rp->enc_alg_list = silc_cipher_get_supported();
+ rp->enc_alg_len = strlen(rp->enc_alg_list);
+
+ /* Get supported hash algorithms */
+ rp->hash_alg_list = silc_hash_get_supported();
+ rp->hash_alg_len = strlen(rp->hash_alg_list);
+
+ /* Get supported HMACs */
+ rp->hmac_alg_list = silc_hmac_get_supported();
+ rp->hmac_alg_len = strlen(rp->hmac_alg_list);
+
+ /* XXX */
+ /* Get supported compression algorithms */
+ rp->comp_alg_list = strdup("none");
+ rp->comp_alg_len = strlen("none");
+
+ rp->len = 1 + 1 + 2 + SILC_SKE_COOKIE_LEN +
+ 2 + rp->version_len +
+ 2 + rp->ke_grp_len + 2 + rp->pkcs_alg_len +
+ 2 + rp->enc_alg_len + 2 + rp->hash_alg_len +
+ 2 + rp->hmac_alg_len + 2 + rp->comp_alg_len;
+
+ return rp;
+}
+
+/* Packet retransmission callback. */
+
+SILC_TASK_CALLBACK(silc_ske_packet_send_retry)
+{
+ SilcSKE ske = context;
+
+ if (ske->retry_count++ >= SILC_SKE_RETRY_COUNT ||
+ ske->aborted) {
+ SILC_LOG_DEBUG(("Retransmission limit reached, packet was lost"));
+ ske->retry_count = 0;
+ ske->retry_timer = SILC_SKE_RETRY_MIN;
+ silc_free(ske->retrans.data);
+ ske->retrans.data = NULL;
+ ske->status = SILC_SKE_STATUS_TIMEOUT;
+ if (ske->responder)
+ silc_fsm_next(&ske->fsm, silc_ske_st_responder_failure);
+ else
+ silc_fsm_next(&ske->fsm, silc_ske_st_initiator_failure);
+ silc_fsm_continue_sync(&ske->fsm);
+ return;
+ }
+
+ SILC_LOG_DEBUG(("Retransmitting packet"));
+ silc_ske_packet_send(ske, ske->retrans.type, ske->retrans.flags,
+ ske->retrans.data, ske->retrans.data_len);
+}
+
+/* Install retransmission timer */
+
+static void silc_ske_install_retransmission(SilcSKE ske)
+{
+ if (!silc_packet_stream_is_udp(ske->stream))
+ return;
+
+ if (ske->retrans.data) {
+ SILC_LOG_DEBUG(("Installing retransmission timer %d secs",
+ ske->retry_timer));
+ silc_schedule_task_add_timeout(ske->schedule, silc_ske_packet_send_retry,
+ ske, ske->retry_timer, 0);
+ }
+ ske->retry_timer = ((ske->retry_timer * SILC_SKE_RETRY_MUL) +
+ (silc_rng_get_rn16(ske->rng) % SILC_SKE_RETRY_RAND));
+}
+
+/* Sends SILC packet. Handles retransmissions with UDP streams. */
+
+static SilcBool silc_ske_packet_send(SilcSKE ske,
+ SilcPacketType type,
+ SilcPacketFlags flags,
+ const unsigned char *data,
+ SilcUInt32 data_len)
+{
+ SilcBool ret;
+
+ /* Send the packet */
+ ret = silc_packet_send(ske->stream, type, flags, data, data_len);
+
+ if (silc_packet_stream_is_udp(ske->stream) &&
+ type != SILC_PACKET_FAILURE && type != SILC_PACKET_REKEY) {
+ silc_free(ske->retrans.data);
+ ske->retrans.type = type;
+ ske->retrans.flags = flags;
+ ske->retrans.data = silc_memdup(data, data_len);
+ ske->retrans.data_len = data_len;
+ silc_ske_install_retransmission(ske);
+ }
+
+ return ret;
+}
+
+/* Calls completion callback. Completion is called always in this function
+ and must not be called anywhere else. */
+
+static void silc_ske_completion(SilcSKE ske)
+{
+ /* Call the completion callback */
+ if (!ske->freed && !ske->aborted && ske->callbacks->completed) {
+ if (ske->status != SILC_SKE_STATUS_OK)
+ ske->callbacks->completed(ske, ske->status, NULL, NULL, NULL,
+ ske->callbacks->context);
+ else
+ ske->callbacks->completed(ske, ske->status, ske->prop, ske->keymat,
+ ske->rekey, ske->callbacks->context);
+ }
+}
+
+/* SKE FSM destructor. */
+
+static void silc_ske_finished(SilcFSM fsm, void *fsm_context,
+ void *destructor_context)
+{
+ SilcSKE ske = fsm_context;
+ ske->running = FALSE;
+ if (ske->freed)
+ silc_ske_free(ske);
+}
+
+/* Key exchange timeout task callback */
+
+SILC_TASK_CALLBACK(silc_ske_timeout)
+{
+ SilcSKE ske = context;
+
+ SILC_LOG_DEBUG(("Timeout"));
+
+ ske->packet = NULL;
+ ske->status = SILC_SKE_STATUS_TIMEOUT;
+ if (ske->responder)
+ silc_fsm_next(&ske->fsm, silc_ske_st_responder_failure);
+ else
+ silc_fsm_next(&ske->fsm, silc_ske_st_initiator_failure);
+
+ silc_fsm_continue_sync(&ske->fsm);
+}
+
+/******************************* Protocol API *******************************/
+
+/* Allocates new SKE object. */
+
+SilcSKE silc_ske_alloc(SilcRng rng, SilcSchedule schedule,
+ SilcSKR repository, SilcPublicKey public_key,
+ SilcPrivateKey private_key, void *context)
+{
+ SilcSKE ske;
+
+ SILC_LOG_DEBUG(("Allocating new Key Exchange object"));
+
+ if (!rng || !schedule)
+ return NULL;
+
+ if (!public_key) {
+ SILC_LOG_ERROR(("Public key must be given to silc_ske_alloc"));
+ return NULL;
+ }
+
+ ske = silc_calloc(1, sizeof(*ske));
+ if (!ske)
+ return NULL;
+ ske->status = SILC_SKE_STATUS_OK;
+ ske->rng = rng;
+ ske->repository = repository;
+ ske->user_data = context;
+ ske->schedule = schedule;
+ ske->public_key = public_key;
+ ske->private_key = private_key;
+ ske->retry_timer = SILC_SKE_RETRY_MIN;
+ ske->refcnt = 1;
+
+ return ske;
+}
+
+/* Free's SKE object. */
+
+void silc_ske_free(SilcSKE ske)
+{
+ SILC_LOG_DEBUG(("Freeing Key Exchange object"));
+
+ if (!ske)
+ return;
+
+ if (ske->running) {
+ ske->freed = TRUE;
+
+ if (ske->aborted) {
+ /* If already aborted, destroy the session immediately */
+ ske->packet = NULL;
+ ske->status = SILC_SKE_STATUS_ERROR;
+ if (ske->responder)
+ silc_fsm_next(&ske->fsm, silc_ske_st_responder_failure);
+ else
+ silc_fsm_next(&ske->fsm, silc_ske_st_initiator_failure);
+ silc_fsm_continue_sync(&ske->fsm);
+ }
+ return;
+ }
+
+ ske->refcnt--;
+ if (ske->refcnt > 0)
+ return;
+
+ /* Free start payload */
+ if (ske->start_payload)
+ silc_ske_payload_start_free(ske->start_payload);
+
+ /* Free KE payload */
+ if (ske->ke1_payload)
+ silc_ske_payload_ke_free(ske->ke1_payload);
+ if (ske->ke2_payload)
+ silc_ske_payload_ke_free(ske->ke2_payload);
+ silc_free(ske->remote_version);
+
+ /* Free rest */
+ if (ske->prop) {
+ if (ske->prop->group)
+ silc_ske_group_free(ske->prop->group);
+ if (ske->prop->cipher)
+ silc_cipher_free(ske->prop->cipher);
+ if (ske->prop->hash)
+ silc_hash_free(ske->prop->hash);
+ if (ske->prop->hmac)
+ silc_hmac_free(ske->prop->hmac);
+ if (ske->prop->public_key)
+ silc_pkcs_public_key_free(ske->prop->public_key);
+ silc_free(ske->prop);
+ }
+ if (ske->keymat)
+ silc_ske_free_key_material(ske->keymat);
+ if (ske->start_payload_copy)
+ silc_buffer_free(ske->start_payload_copy);
+ if (ske->x) {
+ silc_mp_uninit(ske->x);
+ silc_free(ske->x);
+ }
+ if (ske->KEY) {
+ silc_mp_uninit(ske->KEY);
+ silc_free(ske->KEY);
+ }
+ silc_free(ske->retrans.data);
+ silc_free(ske->hash);
+ silc_free(ske->callbacks);
+
+ memset(ske, 'F', sizeof(*ske));
+ silc_free(ske);
+}
+
+/* Return user context */
+
+void *silc_ske_get_context(SilcSKE ske)
+{
+ return ske->user_data;
+}
+
+/* Sets protocol callbacks */
+
+void silc_ske_set_callbacks(SilcSKE ske,
+ SilcSKEVerifyCb verify_key,
+ SilcSKECompletionCb completed,
+ void *context)
+{
+ if (ske->callbacks)
+ silc_free(ske->callbacks);
+ ske->callbacks = silc_calloc(1, sizeof(*ske->callbacks));
+ if (!ske->callbacks)
+ return;
+ ske->callbacks->verify_key = verify_key;
+ ske->callbacks->completed = completed;
+ ske->callbacks->context = context;
+}
+
+
+/******************************** Initiator *********************************/
+
+/* Start protocol. Send our proposal */
+
+SILC_FSM_STATE(silc_ske_st_initiator_start)
+{
+ SilcSKE ske = fsm_context;
+ SilcBuffer payload_buf;
+ SilcStatus status;
+
+ SILC_LOG_DEBUG(("Start"));
+
+ if (ske->aborted) {
+ /** Aborted */
+ silc_fsm_next(fsm, silc_ske_st_initiator_aborted);
+ return SILC_FSM_CONTINUE;
+ }
+
+ /* Encode the payload */
+ status = silc_ske_payload_start_encode(ske, ske->start_payload,
+ &payload_buf);
+ if (status != SILC_SKE_STATUS_OK) {
+ /** Error encoding Start Payload */
+ ske->status = status;
+ silc_fsm_next(fsm, silc_ske_st_initiator_error);
+ return SILC_FSM_CONTINUE;
+ }
+
+ /* Save the the payload buffer for future use. It is later used to
+ compute the HASH value. */
+ ske->start_payload_copy = payload_buf;
+
+ /* Send the packet. */
+ if (!silc_ske_packet_send(ske, SILC_PACKET_KEY_EXCHANGE, 0,
+ silc_buffer_data(payload_buf),
+ silc_buffer_len(payload_buf))) {
+ /** Error sending packet */
+ SILC_LOG_DEBUG(("Error sending packet"));
+ ske->status = SILC_SKE_STATUS_ERROR;
+ silc_fsm_next(fsm, silc_ske_st_initiator_error);
+ return SILC_FSM_CONTINUE;
+ }
+
+ /* Add key exchange timeout */
+ silc_schedule_task_add_timeout(ske->schedule, silc_ske_timeout,
+ ske, ske->timeout, 0);
+
+ /** Wait for responder proposal */
+ SILC_LOG_DEBUG(("Waiting for responder proposal"));
+ silc_fsm_next(fsm, silc_ske_st_initiator_phase1);
+ return SILC_FSM_WAIT;
+}
+
+/* Phase-1. Receives responder's proposal */
+
+SILC_FSM_STATE(silc_ske_st_initiator_phase1)
+{
+ SilcSKE ske = fsm_context;
+ SilcSKEStatus status;
+ SilcSKEStartPayload payload;
+ SilcSKESecurityProperties prop;
+ SilcSKEDiffieHellmanGroup group = NULL;
+ SilcBuffer packet_buf = &ske->packet->buffer;
+ SilcUInt16 remote_port = 0;
+ SilcID id;
+ int coff = 0;
+
+ SILC_LOG_DEBUG(("Start"));
+
+ if (ske->packet->type != SILC_PACKET_KEY_EXCHANGE) {
+ SILC_LOG_DEBUG(("Remote retransmitted an old packet"));
+ silc_ske_install_retransmission(ske);
+ silc_packet_free(ske->packet);
+ ske->packet = NULL;
+ return SILC_FSM_WAIT;
+ }
+
+ /* Decode the payload */
+ status = silc_ske_payload_start_decode(ske, packet_buf, &payload);
+ if (status != SILC_SKE_STATUS_OK) {
+ /** Error decoding Start Payload */
+ silc_packet_free(ske->packet);
+ ske->packet = NULL;
+ ske->status = status;
+ silc_fsm_next(fsm, silc_ske_st_initiator_error);
+ return SILC_FSM_CONTINUE;
+ }
+
+ /* Get remote ID and set it to stream */
+ if (ske->packet->src_id_len) {
+ silc_id_str2id(ske->packet->src_id, ske->packet->src_id_len,
+ ske->packet->src_id_type,
+ (ske->packet->src_id_type == SILC_ID_SERVER ?
+ (void *)&id.u.server_id : (void *)&id.u.client_id),
+ (ske->packet->src_id_type == SILC_ID_SERVER ?
+ sizeof(id.u.server_id) : sizeof(id.u.client_id)));
+ silc_packet_set_ids(ske->stream, 0, NULL, ske->packet->src_id_type,
+ (ske->packet->src_id_type == SILC_ID_SERVER ?
+ (void *)&id.u.server_id : (void *)&id.u.client_id));
+ }
+
+ silc_packet_free(ske->packet);
+ ske->packet = NULL;
+
+ /* Check that the cookie is returned unmodified. In case IV included
+ flag and session port has been set, the first two bytes of cookie
+ are the session port and we ignore them in this check. */
+ if (payload->flags & SILC_SKE_SP_FLAG_IV_INCLUDED && ske->session_port) {
+ /* Take remote port */
+ SILC_GET16_MSB(remote_port, ske->start_payload->cookie);
+ coff = 2;
+ }
+ if (memcmp(ske->start_payload->cookie + coff, payload->cookie + coff,
+ SILC_SKE_COOKIE_LEN - coff)) {
+ /** Invalid cookie */
+ SILC_LOG_ERROR(("Invalid cookie, modified or unsupported feature"));
+ ske->status = SILC_SKE_STATUS_INVALID_COOKIE;
+ silc_fsm_next(fsm, silc_ske_st_initiator_error);
+ return SILC_FSM_CONTINUE;
+ }
+
+ /* Check version string */
+ ske->remote_version = silc_memdup(payload->version, payload->version_len);
+ status = silc_ske_check_version(ske);
+ if (status != SILC_SKE_STATUS_OK) {
+ /** Version mismatch */
+ ske->status = status;
+ silc_fsm_next(fsm, silc_ske_st_initiator_error);
+ return SILC_FSM_CONTINUE;
+ }
+
+ /* Free our KE Start Payload context, we don't need it anymore. */
+ silc_ske_payload_start_free(ske->start_payload);
+ ske->start_payload = NULL;
+
+ /* Take the selected security properties into use while doing
+ the key exchange. This is used only while doing the key
+ exchange. */
+ ske->prop = prop = silc_calloc(1, sizeof(*prop));
+ if (!ske->prop)
+ goto err;
+ prop->flags = payload->flags;
+ status = silc_ske_group_get_by_name(payload->ke_grp_list, &group);
+ if (status != SILC_SKE_STATUS_OK)
+ goto err;
+
+ prop->group = group;
+ prop->remote_port = remote_port;
+
+ if (silc_pkcs_find_algorithm(payload->pkcs_alg_list, NULL) == NULL) {
+ status = SILC_SKE_STATUS_UNKNOWN_PKCS;
+ goto err;
+ }
+ if (silc_cipher_alloc(payload->enc_alg_list, &prop->cipher) == FALSE) {
+ status = SILC_SKE_STATUS_UNKNOWN_CIPHER;
+ goto err;
+ }
+ if (silc_hash_alloc(payload->hash_alg_list, &prop->hash) == FALSE) {
+ status = SILC_SKE_STATUS_UNKNOWN_HASH_FUNCTION;
+ goto err;
+ }
+ if (silc_hmac_alloc(payload->hmac_alg_list, NULL, &prop->hmac) == FALSE) {
+ status = SILC_SKE_STATUS_UNKNOWN_HMAC;
+ goto err;
+ }
+
+ /* Save remote's KE Start Payload */
+ ske->start_payload = payload;
+
+ /** Send KE Payload */
+ silc_fsm_next(fsm, silc_ske_st_initiator_phase2);
+ return SILC_FSM_CONTINUE;
+
+ err:
+ if (payload)
+ silc_ske_payload_start_free(payload);
+ if (group)
+ silc_ske_group_free(group);
+ if (prop->cipher)
+ silc_cipher_free(prop->cipher);
+ if (prop->hash)
+ silc_hash_free(prop->hash);
+ if (prop->hmac)
+ silc_hmac_free(prop->hmac);
+ silc_free(prop);
+ ske->prop = NULL;
+
+ if (status == SILC_SKE_STATUS_OK)
+ status = SILC_SKE_STATUS_ERROR;
+
+ /** Error */
+ ske->status = status;
+ silc_fsm_next(fsm, silc_ske_st_initiator_error);
+ return SILC_FSM_CONTINUE;
+}
+
+/* Phase-2. Send KE payload */
+
+SILC_FSM_STATE(silc_ske_st_initiator_phase2)
+{
+ SilcSKE ske = fsm_context;
+ SilcSKEStatus status;
+ SilcBuffer payload_buf;
+ SilcMPInt *x;
+ SilcSKEKEPayload payload;
+ SilcUInt32 pk_len;
+
+ SILC_LOG_DEBUG(("Start"));
+
+ /* Create the random number x, 1 < x < q. */
+ x = silc_calloc(1, sizeof(*x));
+ if (!x){
+ /** Out of memory */
+ ske->status = SILC_SKE_STATUS_OUT_OF_MEMORY;
+ silc_fsm_next(fsm, silc_ske_st_initiator_error);
+ return SILC_FSM_CONTINUE;
+ }
+ silc_mp_init(x);
+ status =
+ silc_ske_create_rnd(ske, &ske->prop->group->group_order,
+ silc_mp_sizeinbase(&ske->prop->group->group_order, 2),
+ x);
+ if (status != SILC_SKE_STATUS_OK) {
+ /** Error generating random number */
+ silc_mp_uninit(x);
+ silc_free(x);
+ ske->status = status;
+ silc_fsm_next(fsm, silc_ske_st_initiator_error);
+ return SILC_FSM_CONTINUE;
+ }
+
+ /* Encode the result to Key Exchange Payload. */
+
+ payload = silc_calloc(1, sizeof(*payload));
+ if (!payload) {
+ /** Out of memory */
+ silc_mp_uninit(x);
+ silc_free(x);
+ ske->status = SILC_SKE_STATUS_OUT_OF_MEMORY;
+ silc_fsm_next(fsm, silc_ske_st_initiator_error);
+ return SILC_FSM_CONTINUE;
+ }
+ ske->ke1_payload = payload;
+
+ SILC_LOG_DEBUG(("Computing e = g ^ x mod p"));
+
+ /* Do the Diffie Hellman computation, e = g ^ x mod p */
+ silc_mp_init(&payload->x);
+ silc_mp_pow_mod(&payload->x, &ske->prop->group->generator, x,
+ &ske->prop->group->group);
+
+ /* Get public key */
+ payload->pk_data = silc_pkcs_public_key_encode(ske->public_key, &pk_len);
+ if (!payload->pk_data) {
+ /** Error encoding public key */
+ silc_mp_uninit(x);
+ silc_free(x);
+ silc_mp_uninit(&payload->x);
+ silc_free(payload);
+ ske->ke1_payload = NULL;
+ ske->status = SILC_SKE_STATUS_ERROR;
+ silc_fsm_next(fsm, silc_ske_st_initiator_error);
+ return SILC_FSM_CONTINUE;
+ }
+ payload->pk_len = pk_len;
+ payload->pk_type = silc_pkcs_get_type(ske->public_key);
+
+ /* Compute signature data if we are doing mutual authentication */
+ if (ske->private_key && ske->prop->flags & SILC_SKE_SP_FLAG_MUTUAL) {
+ unsigned char hash[SILC_HASH_MAXLEN], sign[2048 + 1];
+ SilcUInt32 hash_len, sign_len;
+
+ SILC_LOG_DEBUG(("We are doing mutual authentication"));
+ SILC_LOG_DEBUG(("Computing HASH_i value"));
+
+ /* Compute the hash value */
+ memset(hash, 0, sizeof(hash));
+ silc_ske_make_hash(ske, hash, &hash_len, TRUE);
+
+ SILC_LOG_DEBUG(("Signing HASH_i value"));
+
+ /* Sign the hash value */
+ if (!silc_pkcs_sign(ske->private_key, hash, hash_len, sign,
+ sizeof(sign) - 1, &sign_len, FALSE, ske->prop->hash)) {
+ /** Error computing signature */
+ silc_mp_uninit(x);
+ silc_free(x);
+ silc_mp_uninit(&payload->x);
+ silc_free(payload->pk_data);
+ silc_free(payload);
+ ske->ke1_payload = NULL;
+ ske->status = SILC_SKE_STATUS_SIGNATURE_ERROR;
+ silc_fsm_next(fsm, silc_ske_st_initiator_error);
+ return SILC_FSM_CONTINUE;
+ }
+ payload->sign_data = silc_memdup(sign, sign_len);
+ if (payload->sign_data)
+ payload->sign_len = sign_len;
+ memset(sign, 0, sizeof(sign));
+ }
+
+ status = silc_ske_payload_ke_encode(ske, payload, &payload_buf);
+ if (status != SILC_SKE_STATUS_OK) {
+ /** Error encoding KE payload */
+ silc_mp_uninit(x);
+ silc_free(x);
+ silc_mp_uninit(&payload->x);
+ silc_free(payload->pk_data);
+ silc_free(payload->sign_data);
+ silc_free(payload);
+ ske->ke1_payload = NULL;
+ ske->status = status;
+ silc_fsm_next(fsm, silc_ske_st_initiator_error);
+ return SILC_FSM_CONTINUE;
+ }
+
+ ske->x = x;
+
+ /* Check for backwards compatibility */
+
+ /* Send the packet. */
+ if (!silc_ske_packet_send(ske, SILC_PACKET_KEY_EXCHANGE_1, 0,
+ silc_buffer_data(payload_buf),
+ silc_buffer_len(payload_buf))) {
+ /** Error sending packet */
+ SILC_LOG_DEBUG(("Error sending packet"));
+ ske->status = SILC_SKE_STATUS_ERROR;
+ silc_fsm_next(fsm, silc_ske_st_initiator_error);
+ return SILC_FSM_CONTINUE;
+ }
+
+ silc_buffer_free(payload_buf);
+
+ /** Waiting responder's KE payload */
+ silc_fsm_next(fsm, silc_ske_st_initiator_phase3);
+ return SILC_FSM_WAIT;
+}
+
+/* Phase-3. Process responder's KE payload */
+
+SILC_FSM_STATE(silc_ske_st_initiator_phase3)
+{
+ SilcSKE ske = fsm_context;
+ SilcSKEStatus status;
+ SilcSKEKEPayload payload;
+ SilcMPInt *KEY;
+ SilcBuffer packet_buf = &ske->packet->buffer;
+
+ SILC_LOG_DEBUG(("Start"));
+
+ if (ske->packet->type != SILC_PACKET_KEY_EXCHANGE_2) {
+ SILC_LOG_DEBUG(("Remote retransmitted an old packet"));
+ silc_ske_install_retransmission(ske);
+ silc_packet_free(ske->packet);
+ ske->packet = NULL;
+ return SILC_FSM_WAIT;
+ }
+
+ /* Decode the payload */
+ status = silc_ske_payload_ke_decode(ske, packet_buf, &payload);
+ if (status != SILC_SKE_STATUS_OK) {
+ /** Error decoding KE payload */
+ silc_packet_free(ske->packet);
+ ske->packet = NULL;
+ ske->status = status;
+ silc_fsm_next(fsm, silc_ske_st_initiator_error);
+ return SILC_FSM_CONTINUE;
+ }
+ silc_packet_free(ske->packet);
+ ske->packet = NULL;
+ ske->ke2_payload = payload;
+
+ if (!payload->pk_data && (ske->callbacks->verify_key || ske->repository)) {
+ SILC_LOG_DEBUG(("Remote end did not send its public key (or certificate), "
+ "even though we require it"));
+ ske->status = SILC_SKE_STATUS_PUBLIC_KEY_NOT_PROVIDED;
+ goto err;
+ }
+
+ SILC_LOG_DEBUG(("Computing KEY = f ^ x mod p"));
+
+ /* Compute the shared secret key */
+ KEY = silc_calloc(1, sizeof(*KEY));
+ silc_mp_init(KEY);
+ silc_mp_pow_mod(KEY, &payload->x, ske->x, &ske->prop->group->group);
+ ske->KEY = KEY;
+
+ /* Decode the remote's public key */
+ if (payload->pk_data &&
+ !silc_pkcs_public_key_alloc(payload->pk_type,
+ payload->pk_data, payload->pk_len,
+ &ske->prop->public_key)) {
+ SILC_LOG_ERROR(("Unsupported/malformed public key received"));
+ status = SILC_SKE_STATUS_UNSUPPORTED_PUBLIC_KEY;
+ goto err;
+ }
+
+ if (ske->prop->public_key && (ske->callbacks->verify_key ||
+ ske->repository)) {
+ SILC_LOG_DEBUG(("Verifying public key"));
+
+ /** Waiting public key verification */
+ silc_fsm_next(fsm, silc_ske_st_initiator_phase4);
+
+ /* If repository is provided, verify the key from there. */
+ if (ske->repository) {
+ SilcSKRFind find;
+
+ find = silc_skr_find_alloc();
+ if (!find) {
+ status = SILC_SKE_STATUS_OUT_OF_MEMORY;
+ goto err;
+ }
+ silc_skr_find_set_pkcs_type(find,
+ silc_pkcs_get_type(ske->prop->public_key));
+ silc_skr_find_set_public_key(find, ske->prop->public_key);
+ silc_skr_find_set_usage(find, SILC_SKR_USAGE_KEY_AGREEMENT);
+
+ /* Find key from repository */
+ SILC_FSM_CALL(silc_skr_find(ske->repository, silc_fsm_get_schedule(fsm),
+ find, silc_ske_skr_callback, ske));
+ } else {
+ /* Verify from application */
+ SILC_FSM_CALL(ske->callbacks->verify_key(ske, ske->prop->public_key,
+ ske->callbacks->context,
+ silc_ske_pk_verified, NULL));
+ }
+ /* NOT REACHED */
+ }
+
+ /** Process key material */
+ silc_fsm_next(fsm, silc_ske_st_initiator_phase4);
+ return SILC_FSM_CONTINUE;
+
+ err:
+ silc_ske_payload_ke_free(payload);
+ ske->ke2_payload = NULL;