child-create: Initiate and handle childless IKE_SAs according to RFC 6023
authorTobias Brunner <tobias@strongswan.org>
Fri, 29 Mar 2019 15:46:59 +0000 (16:46 +0100)
committerTobias Brunner <tobias@strongswan.org>
Thu, 25 Apr 2019 13:23:19 +0000 (15:23 +0200)
src/libcharon/sa/ikev2/tasks/child_create.c

index b80e71d..ac1f999 100644 (file)
@@ -1037,6 +1037,31 @@ static void process_payloads(private_child_create_t *this, message_t *message)
        enumerator->destroy(enumerator);
 }
 
+/**
+ * Check if we should defer the creation of this CHILD_SA until after the
+ * IKE_SA has been established childless.
+ */
+static status_t defer_child_sa(private_child_create_t *this)
+{
+       ike_cfg_t *ike_cfg;
+
+       ike_cfg = this->ike_sa->get_ike_cfg(this->ike_sa);
+
+       if (this->ike_sa->supports_extension(this->ike_sa, EXT_IKE_CHILDLESS))
+       {
+               if (ike_cfg->childless(ike_cfg) == CHILDLESS_FORCE)
+               {
+                       return NEED_MORE;
+               }
+       }
+       else if (ike_cfg->childless(ike_cfg) == CHILDLESS_FORCE)
+       {
+               DBG1(DBG_IKE, "peer does not support childless IKE_SA initiation");
+               return DESTROY_ME;
+       }
+       return NOT_SUPPORTED;
+}
+
 METHOD(task_t, build_i, status_t,
        private_child_create_t *this, message_t *message)
 {
@@ -1067,6 +1092,19 @@ METHOD(task_t, build_i, status_t,
                                /* send only in the first request, not in subsequent rounds */
                                return NEED_MORE;
                        }
+                       switch (defer_child_sa(this))
+                       {
+                               case DESTROY_ME:
+                                       /* config mismatch */
+                                       return DESTROY_ME;
+                               case NEED_MORE:
+                                       /* defer until after IKE_SA has been established */
+                                       chunk_free(&this->my_nonce);
+                                       return NEED_MORE;
+                               default:
+                                       /* just continue to establish the CHILD_SA */
+                                       break;
+                       }
                        break;
                default:
                        break;
@@ -1312,6 +1350,37 @@ static child_cfg_t* select_child_cfg(private_child_create_t *this)
        return child_cfg;
 }
 
+/**
+ * Check how to handle a possibly childless IKE_SA
+ */
+static status_t handle_childless(private_child_create_t *this)
+{
+       ike_cfg_t *ike_cfg;
+
+       ike_cfg = this->ike_sa->get_ike_cfg(this->ike_sa);
+
+       if (!this->proposals && !this->tsi && !this->tsr)
+       {
+               /* looks like a childless IKE_SA, check if we allow it */
+               if (ike_cfg->childless(ike_cfg) == CHILDLESS_NEVER)
+               {
+                       /* we don't allow childless initiation */
+                       DBG1(DBG_IKE, "peer tried to initiate a childless IKE_SA");
+                       return INVALID_STATE;
+               }
+               return SUCCESS;
+       }
+
+       /* the peer apparently wants to create a regular IKE_SA */
+       if (ike_cfg->childless(ike_cfg) == CHILDLESS_FORCE)
+       {
+               /* reject it if we only allow childless initiation */
+               DBG1(DBG_IKE, "peer did not initiate a childless IKE_SA");
+               return INVALID_STATE;
+       }
+       return NOT_SUPPORTED;
+}
+
 METHOD(task_t, build_r, status_t,
        private_child_create_t *this, message_t *message)
 {
@@ -1348,6 +1417,19 @@ METHOD(task_t, build_r, status_t,
                        {       /* no CHILD_SA is created for redirected SAs */
                                return SUCCESS;
                        }
+                       switch (handle_childless(this))
+                       {
+                               case SUCCESS:
+                                       /* no CHILD_SA built */
+                                       return SUCCESS;
+                               case INVALID_STATE:
+                                       message->add_notify(message, FALSE, INVALID_SYNTAX,
+                                                                               chunk_empty);
+                                       return FAILED;
+                               default:
+                                       /* continue with regular initiation */
+                                       break;
+                       }
                        ike_auth = TRUE;
                default:
                        break;
@@ -1533,6 +1615,11 @@ METHOD(task_t, process_i, status_t,
                        {       /* wait until all authentication round completed */
                                return NEED_MORE;
                        }
+                       if (defer_child_sa(this) == NEED_MORE)
+                       {       /* defer until after IKE_SA has been established */
+                               chunk_free(&this->other_nonce);
+                               return NEED_MORE;
+                       }
                        ike_auth = TRUE;
                default:
                        break;