+/************************** Types and definitions ***************************/
+
+/* Structure to hold all SKE callbacks. */
+struct SilcSKECallbacksStruct {
+ SilcSKEVerifyCb verify_key;
+ SilcSKECompletionCb completed;
+ void *context;
+};
+
+/************************ Static utility functions **************************/
+
+/* States */
+SILC_FSM_STATE(silc_ske_st_initiator_start);
+SILC_FSM_STATE(silc_ske_st_initiator_phase1);
+SILC_FSM_STATE(silc_ske_st_initiator_phase2);
+SILC_FSM_STATE(silc_ske_st_initiator_phase3);
+SILC_FSM_STATE(silc_ske_st_initiator_phase4);
+SILC_FSM_STATE(silc_ske_st_initiator_end);
+SILC_FSM_STATE(silc_ske_st_initiator_aborted);
+SILC_FSM_STATE(silc_ske_st_initiator_error);
+SILC_FSM_STATE(silc_ske_st_initiator_failure);
+SILC_FSM_STATE(silc_ske_st_responder_start);
+SILC_FSM_STATE(silc_ske_st_responder_phase1);
+SILC_FSM_STATE(silc_ske_st_responder_phase2);
+SILC_FSM_STATE(silc_ske_st_responder_phase4);
+SILC_FSM_STATE(silc_ske_st_responder_phase5);
+SILC_FSM_STATE(silc_ske_st_responder_end);
+SILC_FSM_STATE(silc_ske_st_responder_aborted);
+SILC_FSM_STATE(silc_ske_st_responder_failure);
+SILC_FSM_STATE(silc_ske_st_responder_error);
+SILC_FSM_STATE(silc_ske_st_rekey_initiator_start);
+SILC_FSM_STATE(silc_ske_st_rekey_initiator_done);
+SILC_FSM_STATE(silc_ske_st_rekey_initiator_end);
+SILC_FSM_STATE(silc_ske_st_rekey_responder_wait);
+SILC_FSM_STATE(silc_ske_st_rekey_responder_start);
+SILC_FSM_STATE(silc_ske_st_rekey_responder_done);
+SILC_FSM_STATE(silc_ske_st_rekey_responder_end);
+SILC_TASK_CALLBACK(silc_ske_packet_send_retry);
+
+SilcSKEKeyMaterial
+silc_ske_process_key_material(SilcSKE ske,
+ SilcUInt32 req_iv_len,
+ SilcUInt32 req_enc_key_len,
+ SilcUInt32 req_hmac_key_len,
+ SilcSKERekeyMaterial *rekey);
+static SilcBool silc_ske_packet_send(SilcSKE ske,
+ SilcPacketType type,
+ SilcPacketFlags flags,
+ const unsigned char *data,
+ SilcUInt32 data_len);
+
+/* Packet callback */
+
+static SilcBool silc_ske_packet_receive(SilcPacketEngine engine,
+ SilcPacketStream stream,
+ SilcPacket packet,
+ void *callback_context,
+ void *app_context)
+{
+ SilcSKE ske = callback_context;
+
+ /* Clear retransmission */
+ ske->retry_timer = SILC_SKE_RETRY_MIN;
+ ske->retry_count = 0;
+ silc_schedule_task_del_by_callback(ske->schedule,
+ silc_ske_packet_send_retry);
+
+ /* Signal for new packet */
+ ske->packet = packet;
+
+ /* Check if we were aborted */
+ if (ske->aborted) {
+ silc_packet_free(packet);
+ ske->packet = NULL;
+
+ if (ske->responder)
+ silc_fsm_next(&ske->fsm, silc_ske_st_responder_aborted);
+ else
+ silc_fsm_next(&ske->fsm, silc_ske_st_initiator_aborted);
+
+ silc_fsm_continue_sync(&ske->fsm);
+ return TRUE;
+ }
+
+ /* See if received failure from remote */
+ if (packet->type == SILC_PACKET_FAILURE) {
+ if (ske->responder)
+ silc_fsm_next(&ske->fsm, silc_ske_st_responder_failure);
+ else
+ silc_fsm_next(&ske->fsm, silc_ske_st_initiator_failure);
+ }
+
+ /* Handle rekey and SUCCESS packets synchronously. After SUCCESS packets
+ they keys are taken into use immediately, hence the synchronous
+ processing to get the keys in use as soon as possible. */
+ if (ske->rekeying || packet->type == SILC_PACKET_SUCCESS)
+ silc_fsm_continue_sync(&ske->fsm);
+ else
+ silc_fsm_continue(&ske->fsm);
+
+ return TRUE;
+}
+
+/* Packet stream callbacks */
+static SilcPacketCallbacks silc_ske_stream_cbs =
+{
+ silc_ske_packet_receive, NULL, NULL
+};
+
+/* Aborts SKE protocol */
+
+static void silc_ske_abort(SilcAsyncOperation op, void *context)
+{
+ SilcSKE ske = context;
+ ske->aborted = TRUE;
+}
+
+/* Public key verification completion callback */
+
+static void silc_ske_pk_verified(SilcSKE ske, SilcSKEStatus status,
+ void *completion_context)
+{
+ ske->status = status;
+ SILC_FSM_CALL_CONTINUE(&ske->fsm);
+}
+
+/* SKR find callback */
+
+static void silc_ske_skr_callback(SilcSKR repository,
+ SilcSKRFind find,
+ SilcSKRStatus status,
+ SilcDList keys, void *context)
+{
+ SilcSKE ske = context;
+
+ silc_skr_find_free(find);
+
+ if (status != SILC_SKR_OK) {
+ if (ske->callbacks->verify_key) {
+ /* Verify from application */
+ ske->callbacks->verify_key(ske, ske->prop->public_key,
+ ske->callbacks->context,
+ silc_ske_pk_verified, NULL);
+ return;
+ }
+ }
+
+ if (keys)
+ silc_dlist_uninit(keys);
+
+ /* Continue */
+ ske->status = (status == SILC_SKR_OK ? SILC_SKE_STATUS_OK :
+ SILC_SKE_STATUS_UNSUPPORTED_PUBLIC_KEY);
+ SILC_FSM_CALL_CONTINUE(&ske->fsm);
+}
+
+/* Checks remote and local versions */
+
+static SilcSKEStatus silc_ske_check_version(SilcSKE ske)
+{
+ SilcUInt32 r_software_version = 0;
+ char *r_software_string = NULL;
+
+ if (!ske->remote_version || !ske->version)
+ return SILC_SKE_STATUS_BAD_VERSION;
+
+ if (!silc_parse_version_string(ske->remote_version, NULL, NULL,
+ &r_software_version,
+ &r_software_string, NULL))
+ return SILC_SKE_STATUS_BAD_VERSION;
+
+ return SILC_SKE_STATUS_OK;
+}
+
+/* Selects the supported security properties from the initiator's Key
+ Exchange Start Payload. A responder function. Saves our reply
+ start payload to ske->start_payload. */
+
+static SilcSKEStatus
+silc_ske_select_security_properties(SilcSKE ske,
+ SilcSKEStartPayload remote_payload,
+ SilcSKESecurityProperties *prop)
+{
+ SilcSKEStatus status;
+ SilcSKEStartPayload rp, payload;
+ char *cp;
+ int len;
+
+ SILC_LOG_DEBUG(("Parsing KE Start Payload"));
+
+ rp = remote_payload;
+
+ /* Check for mandatory fields */
+ if (!rp->ke_grp_len) {
+ SILC_LOG_DEBUG(("KE group not defined in payload"));
+ return SILC_SKE_STATUS_BAD_PAYLOAD;
+ }
+ if (!rp->pkcs_alg_len) {
+ SILC_LOG_DEBUG(("PKCS alg not defined in payload"));
+ return SILC_SKE_STATUS_BAD_PAYLOAD;
+ }
+ if (!rp->enc_alg_len) {
+ SILC_LOG_DEBUG(("Encryption alg not defined in payload"));
+ return SILC_SKE_STATUS_BAD_PAYLOAD;
+ }
+ if (!rp->hash_alg_len) {
+ SILC_LOG_DEBUG(("Hash alg not defined in payload"));
+ return SILC_SKE_STATUS_BAD_PAYLOAD;
+ }
+ if (!rp->hmac_alg_len) {
+ SILC_LOG_DEBUG(("HMAC not defined in payload"));
+ return SILC_SKE_STATUS_BAD_PAYLOAD;
+ }
+
+ /* Allocate security properties */
+ *prop = silc_calloc(1, sizeof(**prop));
+ if (!(*prop))
+ return SILC_SKE_STATUS_OUT_OF_MEMORY;
+
+ /* Allocate our reply start payload */
+ payload = silc_calloc(1, sizeof(*payload));
+ if (!payload) {
+ silc_free(*prop);
+ return SILC_SKE_STATUS_OUT_OF_MEMORY;
+ }
+
+ /* Check version string */
+ ske->remote_version = silc_memdup(rp->version, rp->version_len);
+ status = silc_ske_check_version(ske);
+ if (status != SILC_SKE_STATUS_OK) {
+ ske->status = status;
+ return status;
+ }
+
+ /* Flags are returned unchanged. */
+ (*prop)->flags = payload->flags = rp->flags;
+
+ /* Take cookie, we must return it to sender unmodified. */
+ payload->cookie = silc_calloc(SILC_SKE_COOKIE_LEN, sizeof(unsigned char));
+ if (!payload->cookie) {
+ ske->status = SILC_SKE_STATUS_OUT_OF_MEMORY;
+ return status;
+ }
+ payload->cookie_len = SILC_SKE_COOKIE_LEN;
+ memcpy(payload->cookie, rp->cookie, 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 (rp->flags & SILC_SKE_SP_FLAG_IV_INCLUDED && ske->session_port) {
+ /* Take remote port */
+ SILC_GET16_MSB((*prop)->remote_port, payload->cookie);
+
+ /* Put out port */
+ SILC_PUT16_MSB(ske->session_port, payload->cookie);
+ }
+
+ /* Put our version to our reply */
+ payload->version = strdup(ske->version);
+ if (!payload->version) {
+ ske->status = SILC_SKE_STATUS_OUT_OF_MEMORY;
+ return status;
+ }
+ payload->version_len = strlen(ske->version);
+
+ /* Get supported Key Exchange groups */
+ cp = rp->ke_grp_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 KE group `%s'", item));
+
+ if (silc_ske_group_get_by_name(item, NULL) == SILC_SKE_STATUS_OK) {
+ SILC_LOG_DEBUG(("Found KE group `%s'", item));
+
+ payload->ke_grp_len = len;
+ payload->ke_grp_list = item;
+ break;
+ }
+
+ cp += len;
+ if (strlen(cp) == 0)
+ cp = NULL;
+ else
+ cp++;
+
+ if (item)
+ silc_free(item);
+ }
+
+ if (!payload->ke_grp_len && !payload->ke_grp_list) {
+ SILC_LOG_DEBUG(("Could not find supported KE group"));
+ silc_free(payload);
+ return SILC_SKE_STATUS_UNKNOWN_GROUP;
+ }
+ } else {
+ SILC_LOG_DEBUG(("Proposed KE group `%s'", rp->ke_grp_list));
+ SILC_LOG_DEBUG(("Found KE group `%s'", rp->ke_grp_list));
+
+ payload->ke_grp_len = rp->ke_grp_len;
+ payload->ke_grp_list = strdup(rp->ke_grp_list);
+ }
+
+ /* Save group to security properties */
+ status = silc_ske_group_get_by_name(payload->ke_grp_list, &(*prop)->group);
+ if (status != SILC_SKE_STATUS_OK) {
+ silc_free(payload);
+ return SILC_SKE_STATUS_UNKNOWN_GROUP;
+ }
+
+ /* Get supported PKCS algorithms */
+ cp = rp->pkcs_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 PKCS alg `%s'", item));
+
+ if (silc_pkcs_find_algorithm(item, NULL)) {
+ SILC_LOG_DEBUG(("Found PKCS alg `%s'", item));
+
+ payload->pkcs_alg_len = len;
+ payload->pkcs_alg_list = item;
+ break;
+ }
+
+ cp += len;
+ if (strlen(cp) == 0)
+ cp = NULL;
+ else
+ cp++;
+
+ if (item)
+ silc_free(item);
+ }
+
+ 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,