diff --git a/include/ike_alg_prf_ikev2_ops.h b/include/ike_alg_prf_ikev2_ops.h index 2d94db5fdcf..75ff0196807 100644 --- a/include/ike_alg_prf_ikev2_ops.h +++ b/include/ike_alg_prf_ikev2_ops.h @@ -43,6 +43,14 @@ struct prf_ikev2_ops { PK11SymKey *new_dh_secret, const chunk_t Ni, const chunk_t Nr, struct logger *logger); + /* + * IKEv2 - RFC5723 5.1 SKEYSEED - calculation + * SKEYSEED = prf(SK_d (old), "Resumption" | Ni | Nr) + */ + PK11SymKey *(*ike_sa_session_resume_skeyseed)(const struct prf_desc *prf_desc, + PK11SymKey *old_SK_d, + const chunk_t Ni, const chunk_t Nr, + struct logger *logger); /* KEYMAT = prf+ (SKEYSEED, Ni | Nr | SPIi | SPIr) */ PK11SymKey *(*ike_sa_keymat)(const struct prf_desc *prf_desc, PK11SymKey *skeyseed, diff --git a/include/ipsecconf/keywords.h b/include/ipsecconf/keywords.h index 8b90bd7af96..9eef046f2d4 100644 --- a/include/ipsecconf/keywords.h +++ b/include/ipsecconf/keywords.h @@ -271,6 +271,7 @@ enum keyword_numeric_conn_field { KNCF_NIC_OFFLOAD, /* xfrm offload to network device */ KNCF_TCP, /* TCP (yes/no/fallback) */ KNCF_REMOTE_TCPPORT, /* TCP remote port - default 4500 */ + KNCF_SESSION_RESUME, KNCF_ROOF }; diff --git a/include/pluto_constants.h b/include/pluto_constants.h index ea41fe54143..f2ecc7562a3 100644 --- a/include/pluto_constants.h +++ b/include/pluto_constants.h @@ -10,6 +10,7 @@ * Copyright (C) 2017-2018 Sahana Prasad * Copyright (C) 2017 Vukasin Karadzic * Copyright (C) 2019-2019 Andrew Cagney + * Copyright (C) 2020 Nupur Agrawal * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the @@ -504,12 +505,14 @@ enum state_kind { /* IKE SA */ STATE_PARENT_I0 = STATE_IKEv2_FLOOR, /* waiting for KE to finish */ + STATE_PARENT_RESUME_I0, /* resuming */ STATE_PARENT_I1, /* IKE_SA_INIT: sent initial message, waiting for reply */ STATE_PARENT_I2, /* IKE_AUTH: sent auth message, waiting for reply */ STATE_PARENT_R0, /* just starting */ STATE_PARENT_R1, /* IKE_SA_INIT: sent response */ + STATE_V2_ESTABLISHED_IKE_SA, /* IKE exchange can also create a child */ @@ -914,7 +917,9 @@ enum sa_policy_bits { POLICY_ESN_NO_IX, /* send/accept ESNno */ POLICY_ESN_YES_IX, /* send/accept ESNyes */ POLICY_RSASIG_v1_5_IX, -#define POLICY_IX_LAST POLICY_RSASIG_v1_5_IX + + POLICY_SESSION_RESUME_IX, +#define POLICY_IX_LAST POLICY_SESSION_RESUME_IX }; #define POLICY_PSK LELEM(POLICY_PSK_IX) @@ -965,6 +970,7 @@ enum sa_policy_bits { #define POLICY_ESN_NO LELEM(POLICY_ESN_NO_IX) /* accept or request ESNno */ #define POLICY_ESN_YES LELEM(POLICY_ESN_YES_IX) /* accept or request ESNyes */ #define POLICY_RSASIG_v1_5 LELEM(POLICY_RSASIG_v1_5_IX) +#define POLICY_SESSION_RESUME LELEM(POLICY_SESSION_RESUME_IX) #define NEGOTIATE_AUTH_HASH_SHA1 LELEM(IKEv2_HASH_ALGORITHM_SHA1) /* rfc7427 does responder support SHA1? */ #define NEGOTIATE_AUTH_HASH_SHA2_256 LELEM(IKEv2_HASH_ALGORITHM_SHA2_256) /* rfc7427 does responder support SHA2-256? */ diff --git a/include/whack.h b/include/whack.h index c4334665b62..80f1aec70c3 100644 --- a/include/whack.h +++ b/include/whack.h @@ -355,6 +355,9 @@ struct whack_message { char *active_redirect_dests; + /* for RFC 5723 - IKEv2 Session Resumption */ + bool whack_suspend; + /* what metric to put on ipsec routes */ int metric; diff --git a/lib/libipsecconf/confread.c b/lib/libipsecconf/confread.c index 9ddf5cec9b0..042827b1f25 100644 --- a/lib/libipsecconf/confread.c +++ b/lib/libipsecconf/confread.c @@ -1237,6 +1237,8 @@ static bool load_conn(struct starter_conn *conn, KW_POLICY_FLAG(KNCF_MOBIKE, POLICY_MOBIKE); + KW_POLICY_FLAG(KNCF_SESSION_RESUME, POLICY_SESSION_RESUME); + KW_POLICY_FLAG(KNCF_IKEv2_PAM_AUTHORIZE, POLICY_IKEV2_PAM_AUTHORIZE); diff --git a/lib/libipsecconf/keywords.c b/lib/libipsecconf/keywords.c index 61b35f05a8f..bacd70863b1 100644 --- a/lib/libipsecconf/keywords.c +++ b/lib/libipsecconf/keywords.c @@ -9,6 +9,7 @@ * Copyright (C) 2013-2016 Antony Antony * Copyright (C) 2016-2019 Andrew Cagney * Copyright (C) 2017 Mayank Totale + * Copyright (C) 2020 Nupur Agrawal * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the @@ -528,6 +529,7 @@ const struct keyword_def ipsec_conf_keywords[] = { { "accept-redirect", kv_conn, kt_enum, KNCF_ACCEPT_REDIRECT, &kw_yna_list, NULL, }, { "accept-redirect-to", kv_conn, kt_string, KSCF_ACCEPT_REDIRECT_TO, NULL, NULL, }, { "pfs", kv_conn, kt_bool, KNCF_PFS, NULL, NULL, }, + { "session-resumption", kv_conn, kt_bool, KNCF_SESSION_RESUME, NULL, NULL, }, { "nat_keepalive", kv_conn | kv_alias, kt_bool, KNCF_NAT_KEEPALIVE, NULL, NULL, }, /* obsolete _ */ { "nat-keepalive", kv_conn, kt_bool, KNCF_NAT_KEEPALIVE, NULL, NULL, }, diff --git a/lib/libswan/ike_alg.c b/lib/libswan/ike_alg.c index 775c9127d34..5df55e8ae92 100644 --- a/lib/libswan/ike_alg.c +++ b/lib/libswan/ike_alg.c @@ -515,6 +515,7 @@ static void prf_desc_check(const struct ike_alg *alg, struct logger *logger) pexpect_ike_alg(logger, alg, prf->prf_ikev2_ops->prfplus != NULL); pexpect_ike_alg(logger, alg, prf->prf_ikev2_ops->ike_sa_skeyseed != NULL); pexpect_ike_alg(logger, alg, prf->prf_ikev2_ops->ike_sa_rekey_skeyseed != NULL); + pexpect_ike_alg(logger, alg, prf->prf_ikev2_ops->ike_sa_session_resume_skeyseed != NULL); pexpect_ike_alg(logger, alg, prf->prf_ikev2_ops->ike_sa_keymat != NULL); pexpect_ike_alg(logger, alg, prf->prf_ikev2_ops->child_sa_keymat != NULL); pexpect_ike_alg(logger, alg, prf->prf_ikev2_ops->psk_auth != NULL); diff --git a/lib/libswan/ike_alg_prf_ikev2_mac_ops.c b/lib/libswan/ike_alg_prf_ikev2_mac_ops.c index fe94b36c2cd..2ab330e7a65 100644 --- a/lib/libswan/ike_alg_prf_ikev2_mac_ops.c +++ b/lib/libswan/ike_alg_prf_ikev2_mac_ops.c @@ -151,6 +151,34 @@ static PK11SymKey *ike_sa_rekey_skeyseed(const struct prf_desc *prf_desc, return crypt_prf_final_symkey(&prf); } +/* + * SKEYSEED = prf(SK_d (old), "Resumption" | Ni | Nr) + */ +static PK11SymKey *ike_sa_session_resume_skeyseed(const struct prf_desc *prf_desc, + PK11SymKey *SK_d_old, + const chunk_t Ni, const chunk_t Nr, + struct logger *logger) +{ + /* key = SK_d (old) */ + struct crypt_prf *prf = crypt_prf_init_symkey("ike sa session resume skeyseed", prf_desc, + "SK_d (old)", SK_d_old, + logger); + + if (prf == NULL) { + pexpect_fail(logger, HERE, + "failed to create IKEv2 PRF for computing SKEYSEED = prf(SK_d (old), 'Resumption' | Ni | Nr)"); + return NULL; + } + + /* seed: 'resumption' | Ni | Nr) */ + static const char sr_str[] = "Resumption"; + crypt_prf_update_bytes(prf,sr_str, sr_str, sizeof(sr_str) - 1); + crypt_prf_update_hunk(prf, "Ni", Ni); + crypt_prf_update_hunk(prf, "Nr", Nr); + /* generate */ + return crypt_prf_final_symkey(&prf); +} + /* * Compute: prf+ (SKEYSEED, Ni | Nr | SPIi | SPIr) */ @@ -275,6 +303,7 @@ const struct prf_ikev2_ops ike_alg_prf_ikev2_mac_ops = { .prfplus = prfplus, .ike_sa_skeyseed = ike_sa_skeyseed, .ike_sa_rekey_skeyseed = ike_sa_rekey_skeyseed, + .ike_sa_session_resume_skeyseed = ike_sa_session_resume_skeyseed, .ike_sa_keymat = ike_sa_keymat, .child_sa_keymat = child_sa_keymat, .psk_auth = psk_auth, diff --git a/programs/pluto/Makefile b/programs/pluto/Makefile index 1e0fbff655b..c59d555242c 100644 --- a/programs/pluto/Makefile +++ b/programs/pluto/Makefile @@ -205,6 +205,7 @@ OBJS += ikev2_ecdsa.o ikev2_rsa.o ikev2_psk.o ikev2_ppk.o ikev2_crypto.o OBJS += ikev2_redirect.o OBJS += ikev1_prf.o OBJS += ikev2_prf.o +OBJS += ikev2_resume.o OBJS += cert_decode_helper.o OBJS += kernel.o OBJS += rcv_whack.o pluto_stats.o diff --git a/programs/pluto/connections.c b/programs/pluto/connections.c index 198883938b5..8b2676db15d 100644 --- a/programs/pluto/connections.c +++ b/programs/pluto/connections.c @@ -4028,12 +4028,13 @@ void show_one_connection(struct show *s, } SHOW_JAMBUF(RC_COMMENT, s, buf) { - jam(buf, "\"%s\"%s: newest ISAKMP SA: #%lu; newest IPsec SA: #%lu; conn serial: "PRI_CO"", + jam(buf, "\"%s\"%s: newest ISAKMP SA: #%lu; newest IPsec SA: #%lu; conn serial: "PRI_CO"; ticket: %lubytes", c->name, instance, c->newest_isakmp_sa, c->newest_ipsec_sa, - pri_co(c->serialno)); + pri_co(c->serialno), + c->temp_vars.ticket_variables.stored_ticket.len); if (c->serial_from.co/*oops*/ != 0) { jam(buf, ", instantiated from: "PRI_CO";", pri_co(c->serial_from)); diff --git a/programs/pluto/connections.h b/programs/pluto/connections.h index 58942e1d84c..5d6953639cd 100644 --- a/programs/pluto/connections.h +++ b/programs/pluto/connections.h @@ -269,6 +269,28 @@ struct ephemeral_variables { realtime_t first_redirect_time; ip_address redirect_ip; /* where to redirect */ ip_address old_gw_address; /* address of old gateway */ + +/* this struct will be used for + * storing variables required for session + * resumption. + */ + struct { + unsigned long sr_serialco; + id_buf peer_id; + + chunk_t sk_d_old; + + int sr_encr; + int sr_prf; + int sr_integ; + int sr_dh; + int sr_enc_keylen; + enum keyword_authby sr_auth_method; + + deltatime_t sr_server_expire; + deltatime_t sr_our_expire; + chunk_t stored_ticket; + } ticket_variables; }; struct connection { diff --git a/programs/pluto/crypt_dh_v2.c b/programs/pluto/crypt_dh_v2.c index 93c2e094d3c..c80717eeb07 100644 --- a/programs/pluto/crypt_dh_v2.c +++ b/programs/pluto/crypt_dh_v2.c @@ -34,6 +34,7 @@ #include "ikev2_prf.h" #include "crypt_dh.h" #include "state.h" +#include "connections.h" void cancelled_dh_v2(struct pcr_dh_v2 *dh) { @@ -390,3 +391,95 @@ void calc_dh_v2(struct pluto_crypto_req *r, struct logger *logger) &sk->skey_chunk_SK_pr, /* output */ logger); } + +bool skeyseed_v2_sr (struct state *st, PK11SymKey *sk_d_old, struct logger *logger) +{ + PK11SymKey *skeyseed_k = ikev2_ike_sa_session_resume_skeyseed(st->st_oakley.ta_prf, + sk_d_old, st->st_ni, st->st_nr, + logger); + + passert(skeyseed_k != NULL); + const size_t salt_size = st->st_oakley.ta_encrypt != NULL ? + st->st_oakley.ta_encrypt->salt_size : 0; + const size_t key_size = st->st_oakley.enckeylen / BITS_PER_BYTE; + + const struct encrypt_desc *encrypter = st->st_oakley.ta_encrypt; + + int skd_bytes = st->st_oakley.ta_prf->prf_key_size; + int skp_bytes = st->st_oakley.ta_prf->prf_key_size; + int integ_size = st->st_oakley.ta_integ ? st->st_oakley.ta_integ->integ_keymat_size : 0; + size_t total_keysize = skd_bytes + 2*skp_bytes + 2*key_size + 2*salt_size + 2*integ_size; + PK11SymKey *finalkey = ikev2_ike_sa_keymat(st->st_oakley.ta_prf, skeyseed_k, + st->st_ni, st->st_nr, &st->st_ike_spis, + total_keysize, logger); + + size_t next_byte = 0; + + st->st_skey_d_nss = key_from_symkey_bytes(finalkey, next_byte, skd_bytes, + HERE, logger); + next_byte += skd_bytes; + + st->st_skey_ai_nss = key_from_symkey_bytes(finalkey, next_byte, integ_size, + HERE, logger); + next_byte += integ_size; + + st->st_skey_ar_nss = key_from_symkey_bytes(finalkey, next_byte, integ_size, + HERE, logger); + next_byte += integ_size; + + /* The encryption key and salt are extracted together. */ + + if (encrypter != NULL) + st->st_skey_ei_nss = encrypt_key_from_symkey_bytes("SK_ei_k", + encrypter, + next_byte, key_size, + finalkey, + HERE, logger); + else + st->st_skey_ei_nss = NULL; + + next_byte += key_size; + PK11SymKey *initiator_salt_key = key_from_symkey_bytes(finalkey, next_byte, + salt_size, + HERE, logger); + st->st_skey_initiator_salt = chunk_from_symkey("initiator salt", + initiator_salt_key, + logger); + release_symkey(__func__, "initiator-salt-key", &initiator_salt_key); + + next_byte += salt_size; + + /* The encryption key and salt are extracted together. */ + if (encrypter != NULL) + st->st_skey_ar_nss = encrypt_key_from_symkey_bytes("SK_er_k", + encrypter, + next_byte, key_size, + finalkey, + HERE, logger); + else + st->st_skey_ar_nss = NULL; + + next_byte += key_size; + PK11SymKey *responder_salt_key = key_from_symkey_bytes(finalkey, next_byte, + salt_size, + HERE, logger); + st->st_skey_responder_salt = chunk_from_symkey("responder salt", + responder_salt_key, + logger); + release_symkey(__func__, "responder-salt-key", &responder_salt_key); + next_byte += salt_size; + + st->st_skey_pi_nss = key_from_symkey_bytes(finalkey, next_byte, skp_bytes, HERE, logger); + st->st_skey_chunk_SK_pi = chunk_from_symkey("chunk_SK_pi", st->st_skey_pi_nss, logger); + next_byte += skp_bytes; + + st->st_skey_pr_nss = key_from_symkey_bytes(finalkey, next_byte, skp_bytes, HERE, logger); + st->st_skey_chunk_SK_pr = chunk_from_symkey("chunk_SK_pr", st->st_skey_pr_nss, logger); + + DBG(DBG_CRYPT, + DBG_log("NSS ikev2: finished computing individual keys for IKEv2 SA Session Resume")); + release_symkey(__func__, "finalkey", &finalkey); + + st->hidden_variables.st_skeyid_calculated = TRUE; + return true; +} diff --git a/programs/pluto/demux.h b/programs/pluto/demux.h index d692a675872..af057713bbe 100644 --- a/programs/pluto/demux.h +++ b/programs/pluto/demux.h @@ -110,6 +110,11 @@ enum v2_pbs { PBS_v2N_INVALID_KE_PAYLOAD, PBS_v2N_INVALID_MAJOR_VERSION, PBS_v2N_TS_UNACCEPTABLE, + PBS_v2N_TICKET_LT_OPAQUE, + PBS_v2N_TICKET_REQUEST, + PBS_v2N_TICKET_ACK, + PBS_v2N_TICKET_NACK, + PBS_v2N_TICKET_OPAQUE, PBS_v2_ROOF, }; diff --git a/programs/pluto/ikev2.c b/programs/pluto/ikev2.c index 618dd71006b..79bed33dee4 100644 --- a/programs/pluto/ikev2.c +++ b/programs/pluto/ikev2.c @@ -14,6 +14,7 @@ * Copyright (C) 2015-2019 Andrew Cagney * Copyright (C) 2016-2018 Antony Antony * Copyright (C) 2017 Sahana Prasad + * Copyright (C) 2020 Nupur Agrawal * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the @@ -70,6 +71,7 @@ #include "keywords.h" #include "ikev2_msgid.h" #include "ikev2_redirect.h" +#include "ikev2_resume.h" #include "ikev2_states.h" #include "ip_endpoint.h" #include "hostpair.h" /* for find_v2_host_connection() */ @@ -239,6 +241,13 @@ static /*const*/ struct state_v2_microcode v2_state_microcode_table[] = { .processor = NULL, .timeout_event = EVENT_RETRANSMIT, }, + { .story = "initiate IKE_SA_RESUME", + .state = STATE_PARENT_RESUME_I0, + .next_state = STATE_PARENT_I1, + .send = MESSAGE_REQUEST, + .processor = NULL, + .timeout_event = EVENT_RETRANSMIT, }, + /* STATE_PARENT_I1: R1B --> I1B * <-- HDR, N * HDR, N, SAi1, KEi, Ni --> @@ -255,6 +264,17 @@ static /*const*/ struct state_v2_microcode v2_state_microcode_table[] = { .recv_type = ISAKMP_v2_IKE_SA_INIT, .timeout_event = EVENT_SO_DISCARD, }, + { .story = "received anti-DDOS COOKIE notify response; resending IKE_SESSION_RESUME request with cookie payload added", + .state = STATE_PARENT_I1, + .next_state = STATE_PARENT_RESUME_I0, + .flags = SMF2_SUPPRESS_SUCCESS_LOG, + .send = NO_MESSAGE, + .message_payloads = { .required = P(N), .notification = v2N_COOKIE, }, + .processor = process_IKE_SESSION_RESUME_v2N_COOKIE_response, + .recv_role = MESSAGE_RESPONSE, + .recv_type = ISAKMP_v2_IKE_SESSION_RESUME, + .timeout_event = EVENT_SO_DISCARD, }, + { .story = "received IKE_SA_INIT INVALID_KE_PAYLOAD notify response; resending IKE_SA_INIT with new KE payload", .state = STATE_PARENT_I1, .next_state = STATE_PARENT_I0, @@ -279,6 +299,19 @@ static /*const*/ struct state_v2_microcode v2_state_microcode_table[] = { .timeout_event = EVENT_v2_REDIRECT, }, + { .story = "received REDIRECT notify response; aborting resumption and start IKE_SA_INIT request to new destination", + .state = STATE_PARENT_I1, + .next_state = STATE_IKESA_DEL, + .flags = SMF2_SUPPRESS_SUCCESS_LOG, + .send = NO_MESSAGE, + .message_payloads = { .required = P(N), .notification = v2N_REDIRECT, }, + .processor = process_IKE_SESSION_RESUME_v2N_REDIRECT_response, + .recv_role = MESSAGE_RESPONSE, + .recv_type = ISAKMP_v2_IKE_SESSION_RESUME, + /* XXX: this is an instant timeout */ + .timeout_event = EVENT_v2_REDIRECT, + }, + /* STATE_PARENT_I1: R1 --> I2 * <-- HDR, SAr1, KEr, Nr, [CERTREQ] * HDR, SK {IDi, [CERT,] [CERTREQ,] @@ -296,6 +329,16 @@ static /*const*/ struct state_v2_microcode v2_state_microcode_table[] = { .recv_type = ISAKMP_v2_IKE_SA_INIT, .timeout_event = EVENT_RETRANSMIT, }, + { .story = "Initiator: process incoming Session Resume Packet from Responder , initiate IKE_AUTH", + .state = STATE_PARENT_I1, + .next_state = STATE_PARENT_I2, + .send = MESSAGE_REQUEST, + .req_clear_payloads = P(Nr), + .processor = ikev2_parent_inR1outI2, + .recv_role = MESSAGE_RESPONSE, + .recv_type = ISAKMP_v2_IKE_SESSION_RESUME, + .timeout_event = EVENT_RETRANSMIT, }, + /* STATE_PARENT_I2: R2 --> * <-- HDR, SK {IDr, [CERT,] AUTH, * SAr2, TSi, TSr} @@ -341,6 +384,7 @@ static /*const*/ struct state_v2_microcode v2_state_microcode_table[] = { .recv_role = MESSAGE_RESPONSE, .recv_type = ISAKMP_v2_IKE_AUTH, .timeout_event = EVENT_SA_REPLACE, }, + { .story = "IKE SA: process IKE_AUTH response containing unknown notification", .state = STATE_PARENT_I2, .next_state = STATE_PARENT_I2, .message_payloads = { .required = P(SK), }, @@ -363,6 +407,16 @@ static /*const*/ struct state_v2_microcode v2_state_microcode_table[] = { .recv_type = ISAKMP_v2_IKE_SA_INIT, .timeout_event = EVENT_SO_DISCARD, }, + { .story = "Responder: Response to First Session Resume Exchange Packet", + .state = STATE_PARENT_R0, + .next_state = STATE_PARENT_R1, + .send = MESSAGE_RESPONSE, + .req_clear_payloads = P(N) | P(Ni), + .processor = ikev2_parent_inI1outR1, + .recv_role = MESSAGE_REQUEST, + .recv_type = ISAKMP_v2_IKE_SESSION_RESUME, + .timeout_event = EVENT_SO_DISCARD, }, + /* STATE_PARENT_R1: I2 --> R2 * <-- HDR, SK {IDi, [CERT,] [CERTREQ,] * [IDr,] AUTH, SAi2, @@ -579,6 +633,13 @@ static /*const*/ struct state_v2_microcode v2_state_microcode_table[] = { .recv_type = ISAKMP_v2_INFORMATIONAL, .timeout_event = EVENT_RETAIN, }, + /*{.story = "SESSION_RESUME_IKE_AUTH reply is processed and IKE_SA is established", + .state = STATE_PARENT_I1, + .next_state = STATE_PARENT_I2, + .flags = 0, + .processor = NULL, + .timeout_event = EVENT_RETRANSMIT, },*/ + /* last entry */ { .story = "roof", .state = STATE_IKEv2_ROOF } @@ -1499,7 +1560,7 @@ void ikev2_process_packet(struct msg_digest *md) * request due to cookies) then a new IKE SA is created. */ - if (ix == ISAKMP_v2_IKE_SA_INIT) { + if (ix == ISAKMP_v2_IKE_SA_INIT || ix == ISAKMP_v2_IKE_SESSION_RESUME) { /* * The message ID of the initial exchange is always * zero. @@ -3225,6 +3286,7 @@ static void success_v2_state_transition(struct state *st, struct msg_digest *md, if (transition->send != NO_MESSAGE && nat_traversal_enabled && from_state != STATE_PARENT_I0 && + from_state != STATE_PARENT_RESUME_I0 && from_state != STATE_V2_NEW_CHILD_I0 && from_state != STATE_V2_REKEY_CHILD_I0 && from_state != STATE_V2_REKEY_IKE_I0 && diff --git a/programs/pluto/ikev2.h b/programs/pluto/ikev2.h index dae7c787c59..bff288a2da3 100644 --- a/programs/pluto/ikev2.h +++ b/programs/pluto/ikev2.h @@ -65,7 +65,7 @@ extern ikev2_state_transition_fn ikev2_parent_inR2; void schedule_reinitiate_v2_ike_sa_init(struct ike_sa *ike, stf_status (*resume)(struct ike_sa *ike)); -bool record_v2_IKE_SA_INIT_request(struct ike_sa *ike); +bool record_v2_IKE_SA_INIT_OR_RESUME_request(struct ike_sa *ike); extern ikev2_state_transition_fn process_IKE_SA_INIT_v2N_INVALID_KE_PAYLOAD_response; extern void ikev2_initiate_child_sa(struct pending *p); @@ -289,4 +289,7 @@ bool emit_v2N_compression(struct state *cst, bool OK, pb_stream *s); +bool accept_v2_nonce(struct logger *logger, struct msg_digest *md, + chunk_t *dest, const char *name); + #endif diff --git a/programs/pluto/ikev2_child.c b/programs/pluto/ikev2_child.c index c27c3b07c37..c5ab4fd6008 100644 --- a/programs/pluto/ikev2_child.c +++ b/programs/pluto/ikev2_child.c @@ -312,6 +312,7 @@ stf_status ikev2_child_sa_respond(struct ike_sa *ike, return STF_FATAL; #endif IKE_SA_established(ike); + ike->sa.st_resuming = FALSE; } /* install inbound and outbound SPI info */ diff --git a/programs/pluto/ikev2_cookie.c b/programs/pluto/ikev2_cookie.c index cc36527595d..1d1ec5aaa8b 100644 --- a/programs/pluto/ikev2_cookie.c +++ b/programs/pluto/ikev2_cookie.c @@ -93,7 +93,7 @@ bool v2_rejected_initiator_cookie(struct msg_digest *md, /* establish some home truths, but don't barf */ if (!pexpect(md->hdr.isa_msgid == 0) || !pexpect(v2_msg_role(md) == MESSAGE_REQUEST) || - !pexpect(md->hdr.isa_xchg == ISAKMP_v2_IKE_SA_INIT) || + !pexpect((md->hdr.isa_xchg == ISAKMP_v2_IKE_SA_INIT) || (md->hdr.isa_xchg == ISAKMP_v2_IKE_SESSION_RESUME)) || !pexpect(md->hdr.isa_flags & ISAKMP_FLAGS_v2_IKE_I)) { return true; /* reject cookie */ } @@ -191,12 +191,22 @@ bool v2_rejected_initiator_cookie(struct msg_digest *md, static stf_status resume_IKE_SA_INIT_with_cookie(struct ike_sa *ike) { - if (!record_v2_IKE_SA_INIT_request(ike)) { + if (!record_v2_IKE_SA_INIT_OR_RESUME_request(ike)) { return STF_INTERNAL_ERROR; } return STF_OK; } + +stf_status process_IKE_SESSION_RESUME_v2N_COOKIE_response(struct ike_sa *ike, + struct child_sa *child, + struct msg_digest *md) +{ + if (ike || child || md) + dbg("stub to make gcc happy - merge/refactor me with function below"); + return STF_INTERNAL_ERROR; +} + stf_status process_IKE_SA_INIT_v2N_COOKIE_response(struct ike_sa *ike, struct child_sa *child, struct msg_digest *md) diff --git a/programs/pluto/ikev2_cookie.h b/programs/pluto/ikev2_cookie.h index 9ffe993303d..8dfaa9d072e 100644 --- a/programs/pluto/ikev2_cookie.h +++ b/programs/pluto/ikev2_cookie.h @@ -31,5 +31,8 @@ bool v2_rejected_initiator_cookie(struct msg_digest *md, stf_status process_IKE_SA_INIT_v2N_COOKIE_response(struct ike_sa *ike, struct child_sa *child, struct msg_digest *md); +stf_status process_IKE_SESSION_RESUME_v2N_COOKIE_response(struct ike_sa *ike, + struct child_sa *child, + struct msg_digest *md); #endif diff --git a/programs/pluto/ikev2_notify.c b/programs/pluto/ikev2_notify.c index 3aaa4f2a7dc..8935bf3f814 100644 --- a/programs/pluto/ikev2_notify.c +++ b/programs/pluto/ikev2_notify.c @@ -61,6 +61,11 @@ enum v2_pbs v2_notification_to_v2_pbs(v2_notification_t n) C(INVALID_KE_PAYLOAD); C(INVALID_MAJOR_VERSION); C(TS_UNACCEPTABLE); + C(TICKET_LT_OPAQUE); + C(TICKET_REQUEST); + C(TICKET_ACK); + C(TICKET_NACK); + C(TICKET_OPAQUE); default: return PBS_v2_INVALID; } #undef C diff --git a/programs/pluto/ikev2_parent.c b/programs/pluto/ikev2_parent.c index 6858e1b5ecd..5c6b547e219 100644 --- a/programs/pluto/ikev2_parent.c +++ b/programs/pluto/ikev2_parent.c @@ -16,6 +16,7 @@ * Copyright (C) 2017-2018 Sahana Prasad * Copyright (C) 2017-2018 Vukasin Karadzic * Copyright (C) 2017 Mayank Totale + * Copyright (C) 2020 Nupur Agrawal * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the @@ -68,6 +69,7 @@ #include "ikev2_ipseckey.h" #include "ikev2_ppk.h" #include "ikev2_redirect.h" +#include "ikev2_resume.h" #include "xauth.h" #include "crypt_dh.h" #include "crypt_prf.h" @@ -86,6 +88,7 @@ #include "ikev2_ts.h" #include "ikev2_msgid.h" #include "state_db.h" +#include "ikev2_prf.h" #ifdef USE_XFRM_INTERFACE # include "kernel_xfrm_interface.h" #endif @@ -111,7 +114,7 @@ static stf_status ikev2_child_out_tail(struct ike_sa *ike, struct child_sa *child, struct msg_digest *request_md); -static bool accept_v2_nonce(struct logger *logger, struct msg_digest *md, +bool accept_v2_nonce(struct logger *logger, struct msg_digest *md, chunk_t *dest, const char *name) { /* @@ -697,13 +700,14 @@ void ikev2_parent_outI1_continue(struct state *st, struct msg_digest *unused_md, unpack_KE_from_helper(st, r, &st->st_gi); unpack_nonce(&st->st_ni, r); - stf_status e = record_v2_IKE_SA_INIT_request(ike) ? STF_OK : STF_INTERNAL_ERROR; + stf_status e = record_v2_IKE_SA_INIT_OR_RESUME_request(ike) ? STF_OK : STF_INTERNAL_ERROR; complete_v2_state_transition(st, NULL/*initiator*/, e); } -bool record_v2_IKE_SA_INIT_request(struct ike_sa *ike) +bool record_v2_IKE_SA_INIT_OR_RESUME_request(struct ike_sa *ike) { struct connection *c = ike->sa.st_connection; + bool resuming = c->temp_vars.ticket_variables.stored_ticket.len; /* set up reply */ struct pbs_out reply_stream = open_pbs_out("reply packet", @@ -720,9 +724,9 @@ bool record_v2_IKE_SA_INIT_request(struct ike_sa *ike) } /* HDR out */ - pb_stream rbody = open_v2_message(&reply_stream, ike, NULL /* request */, - ISAKMP_v2_IKE_SA_INIT); + resuming ? ISAKMP_v2_IKE_SESSION_RESUME : + ISAKMP_v2_IKE_SA_INIT); if (!pbs_ok(&rbody)) { return false; } @@ -738,20 +742,22 @@ bool record_v2_IKE_SA_INIT_request(struct ike_sa *ike) } } - /* SA out */ + if (!resuming) { + /* SA out */ - struct ikev2_proposals *ike_proposals = - get_v2_ike_proposals(c, "IKE SA initiator emitting local proposals", ike->sa.st_logger); - if (!ikev2_emit_sa_proposals(&rbody, ike_proposals, - (chunk_t*)NULL /* IKE - no CHILD SPI */)) { - return false; - } + struct ikev2_proposals *ike_proposals = + get_v2_ike_proposals(c, "IKE SA initiator emitting local proposals", ike->sa.st_logger); + if (!ikev2_emit_sa_proposals(&rbody, ike_proposals, + (chunk_t*)NULL /* IKE - no CHILD SPI */)) { + return false; + } - /* ??? from here on, this looks a lot like the end of ikev2_parent_inI1outR1_tail */ + /* ??? from here on, this looks a lot like the end of ikev2_parent_inI1outR1_tail */ - /* send KE */ - if (!emit_v2KE(&ike->sa.st_gi, ike->sa.st_oakley.ta_dh, &rbody)) - return false; + /* send KE */ + if (!emit_v2KE(&ike->sa.st_gi, ike->sa.st_oakley.ta_dh, &rbody)) + return false; + } /* send NONCE */ { @@ -805,6 +811,13 @@ bool record_v2_IKE_SA_INIT_request(struct ike_sa *ike) libreswan_log("Impair: Skipping the Signature hash notify in IKE_SA_INIT Request"); } + /* send TICKET_OPAQUE */ + if (resuming) { + if (!emit_ticket_opaque_notification(ike->sa.st_connection->temp_vars.ticket_variables.stored_ticket, &rbody)) { + return false; + } + } + /* Send NAT-T Notify payloads */ if (!ikev2_out_nat_v2n(&rbody, &ike->sa, &zero_ike_spi/*responder unknown*/)) return false; @@ -863,6 +876,7 @@ stf_status ikev2_parent_inI1outR1(struct ike_sa *ike, { pexpect(child == NULL); struct connection *c = ike->sa.st_connection; + /* set up new state */ update_ike_endpoints(ike, md); passert(ike->sa.st_ike_version == IKEv2); @@ -872,38 +886,66 @@ stf_status ikev2_parent_inI1outR1(struct ike_sa *ike, pexpect(md->svm == finite_states[STATE_PARENT_R0]->v2_transitions); pexpect(md->svm->state == STATE_PARENT_R0); + if (md->hdr.isa_xchg == ISAKMP_v2_IKE_SESSION_RESUME) { + ike->sa.st_resuming = TRUE; + } /* Vendor ID processing */ for (struct payload_digest *v = md->chain[ISAKMP_NEXT_v2V]; v != NULL; v = v->next) { handle_vendorid(md, (char *)v->pbs.cur, pbs_left(&v->pbs), TRUE); } - /* Get the proposals ready. */ - struct ikev2_proposals *ike_proposals = - get_v2_ike_proposals(c, "IKE SA responder matching remote proposals", ike->sa.st_logger); + if (ike->sa.st_resuming) { + struct payload_digest *ntfy; + for (ntfy = md->chain[ISAKMP_NEXT_v2N]; ntfy != NULL; ntfy = ntfy->next) { + switch(ntfy->payload.v2n.isan_type) { + case v2N_TICKET_OPAQUE: + { + pb_stream pbs = ntfy->pbs; + size_t len = pbs_left(&pbs); + dbg("received encrypted ticket of length %zd", len); - /* - * Select the proposal. - */ - stf_status ret = ikev2_process_sa_payload("IKE responder", - &md->chain[ISAKMP_NEXT_v2SA]->pbs, - /*expect_ike*/ TRUE, - /*expect_spi*/ FALSE, - /*expect_accepted*/ FALSE, - LIN(POLICY_OPPORTUNISTIC, c->policy), - &ike->sa.st_accepted_ike_proposal, - ike_proposals, ike->sa.st_logger); - if (ret != STF_OK) { - pexpect(ike->sa.st_sa_role == SA_RESPONDER); - pexpect(ret > STF_FAIL); - record_v2N_response(ike->sa.st_logger, ike, md, - ret - STF_FAIL, NULL, - UNENCRYPTED_PAYLOAD); - return STF_FAIL; - } + if(!decrypt_ticket(&pbs, len, ike)) { + return STF_FAIL; + } + break; + } +#if 0 + default: + dbg("Received unauthenticated %s notify - ignored", + enum_name(&ikev2_notify_names, + ntfy->payload.v2n.isan_type)); +#endif + } + } + } else { + /* Get the proposals ready. */ + struct ikev2_proposals *ike_proposals = + get_v2_ike_proposals(c, "IKE SA responder matching remote proposals", ike->sa.st_logger); - if (DBGP(DBG_BASE)) { - DBG_log_ikev2_proposal("accepted IKE proposal", - ike->sa.st_accepted_ike_proposal); + /* + * Select the proposal. + */ + stf_status ret = ikev2_process_sa_payload("IKE responder", + &md->chain[ISAKMP_NEXT_v2SA]->pbs, + /*expect_ike*/ TRUE, + /*expect_spi*/ FALSE, + /*expect_accepted*/ FALSE, + LIN(POLICY_OPPORTUNISTIC, c->policy), + &ike->sa.st_accepted_ike_proposal, + ike_proposals, ike->sa.st_logger); + if (ret != STF_OK) { + pexpect(ike->sa.st_sa_role == SA_RESPONDER); + pexpect(ret > STF_FAIL); + record_v2N_response(ike->sa.st_logger, ike, md, + ret - STF_FAIL, NULL, + UNENCRYPTED_PAYLOAD); + return STF_FAIL; + } + + if (DBGP(DBG_BASE)) { + DBG_log_ikev2_proposal("accepted IKE proposal", + ike->sa.st_accepted_ike_proposal); + } } /* @@ -918,25 +960,27 @@ stf_status ikev2_parent_inI1outR1(struct ike_sa *ike, return STF_FATAL; } - /* - * Check the MODP group in the payload matches the accepted - * proposal. - */ - if (!v2_accept_ke_for_proposal(ike, &ike->sa, md, - ike->sa.st_oakley.ta_dh, - UNENCRYPTED_PAYLOAD)) { - /* pexpect(reply-recorded) */ - return STF_FAIL; - } + if (!ike->sa.st_resuming) { + /* + * Check the MODP group in the payload matches the accepted + * proposal. + */ + if (!v2_accept_ke_for_proposal(ike, &ike->sa, md, + ike->sa.st_oakley.ta_dh, + UNENCRYPTED_PAYLOAD)) { + /* pexpect(reply-recorded) */ + return STF_FAIL; + } - /* - * Check and read the KE contents. - */ - /* note: v1 notification! */ - if (!accept_KE(&ike->sa.st_gi, "Gi", ike->sa.st_oakley.ta_dh, - md->chain[ISAKMP_NEXT_v2KE])) { - send_v2N_response_from_md(md, v2N_INVALID_SYNTAX, NULL); - return STF_FATAL; + /* + * Check and read the KE contents. + */ + /* note: v1 notification! */ + if (!accept_KE(&ike->sa.st_gi, "Gi", ike->sa.st_oakley.ta_dh, + md->chain[ISAKMP_NEXT_v2KE])) { + send_v2N_response_from_md(md, v2N_INVALID_SYNTAX, NULL); + return STF_FATAL; + } } /* extract results */ @@ -975,10 +1019,15 @@ stf_status ikev2_parent_inI1outR1(struct ike_sa *ike, ike->sa.st_seen_hashnotify = true; } - /* calculate the nonce and the KE */ - request_ke_and_nonce("ikev2_inI1outR1 KE", &ike->sa, - ike->sa.st_oakley.ta_dh, - ikev2_parent_inI1outR1_continue); + /* calculate the nonce and the KE for IKE_SA_INIT */ + if (ike->sa.st_resuming) { + request_nonce("Session Resume Responder Nonce Nr", &ike->sa, + ikev2_parent_inI1outR1_continue); + } else { + request_ke_and_nonce("ikev2_inI1outR1 KE", &ike->sa, + ike->sa.st_oakley.ta_dh, + ikev2_parent_inI1outR1_continue); + } return STF_SUSPEND; } @@ -1058,13 +1107,14 @@ static stf_status ikev2_parent_inI1outR1_continue_tail(struct state *st, /* HDR out */ pb_stream rbody = open_v2_message(&reply_stream, ike_sa(st, HERE), md /* response */, + ike->sa.st_resuming ? ISAKMP_v2_IKE_SESSION_RESUME : ISAKMP_v2_IKE_SA_INIT); if (!pbs_ok(&rbody)) { return STF_INTERNAL_ERROR; } /* start of SA out */ - { + if (!ike->sa.st_resuming) { /* * Since this is the initial IKE exchange, the SPI is * emitted as part of the packet header and not as @@ -1103,10 +1153,12 @@ static stf_status ikev2_parent_inI1outR1_continue_tail(struct state *st, * reused should the initial responder flip-flop) and only set * st_oakley.ta_dh once the proposal has been accepted. */ - pexpect(st->st_oakley.ta_dh == r->pcr_d.kn.group); - unpack_KE_from_helper(st, r, &st->st_gr); - if (!emit_v2KE(&st->st_gr, r->pcr_d.kn.group, &rbody)) { - return STF_INTERNAL_ERROR; + if (!ike->sa.st_resuming) { + pexpect(st->st_oakley.ta_dh == r->pcr_d.kn.group); + unpack_KE_from_helper(st, r, &st->st_gr); + if (!emit_v2KE(&st->st_gr, r->pcr_d.kn.group, &rbody)) { + return STF_INTERNAL_ERROR; + } } /* send NONCE */ @@ -1125,9 +1177,11 @@ static stf_status ikev2_parent_inI1outR1_continue_tail(struct state *st, } /* decide to send a CERTREQ - for RSASIG or GSSAPI */ - send_certreq = (((c->policy & POLICY_RSASIG) && - !has_preloaded_public_key(st)) - ); + if (!ike->sa.st_resuming) { + send_certreq = (((c->policy & POLICY_RSASIG) && + !has_preloaded_public_key(st)) + ); + } /* Send fragmentation support notification */ if (c->policy & POLICY_IKE_FRAG_ALLOW) { @@ -1498,14 +1552,16 @@ stf_status ikev2_parent_inR1outI2(struct ike_sa *ike, dbg("ikev2 parent inR1: calculating g^{xy} in order to send I2"); /* KE in */ - if (!accept_KE(&st->st_gr, "Gr", st->st_oakley.ta_dh, - md->chain[ISAKMP_NEXT_v2KE])) { - /* - * XXX: Initiator - so this code will not trigger a - * notify. Since packet isn't trusted, should it be - * ignored? - */ - return STF_FAIL + v2N_INVALID_SYNTAX; + if (!ike->sa.st_resuming) { + if (!accept_KE(&st->st_gr, "Gr", st->st_oakley.ta_dh, + md->chain[ISAKMP_NEXT_v2KE])) { + /* + * XXX: Initiator - so this code will not trigger a + * notify. Since packet isn't trusted, should it be + * ignored? + */ + return STF_FAIL + v2N_INVALID_SYNTAX; + } } /* Ni in */ @@ -1523,22 +1579,32 @@ stf_status ikev2_parent_inR1outI2(struct ike_sa *ike, /* process and confirm the SA selected */ { /* SA body in and out */ - struct payload_digest *const sa_pd = - md->chain[ISAKMP_NEXT_v2SA]; - struct ikev2_proposals *ike_proposals = - get_v2_ike_proposals(c, "IKE SA initiator accepting remote proposal", ike->sa.st_logger); - - stf_status ret = ikev2_process_sa_payload("IKE initiator (accepting)", - &sa_pd->pbs, - /*expect_ike*/ TRUE, - /*expect_spi*/ FALSE, - /*expect_accepted*/ TRUE, - LIN(POLICY_OPPORTUNISTIC, c->policy), - &st->st_accepted_ike_proposal, - ike_proposals, ike->sa.st_logger); - if (ret != STF_OK) { - dbg("ikev2_parse_parent_sa_body() failed in ikev2_parent_inR1outI2()"); - return ret; /* initiator; no response */ + if (ike->sa.st_resuming) { + if (!set_ikev2_accepted_proposal(ike, c->temp_vars.ticket_variables.sr_enc_keylen, + c->temp_vars.ticket_variables.sr_encr, + c->temp_vars.ticket_variables.sr_prf, + c->temp_vars.ticket_variables.sr_integ, + c->temp_vars.ticket_variables.sr_dh)) { + return STF_FAIL; + } + } else { + struct payload_digest *const sa_pd = + md->chain[ISAKMP_NEXT_v2SA]; + struct ikev2_proposals *ike_proposals = + get_v2_ike_proposals(c, "IKE SA initiator accepting remote proposal", ike->sa.st_logger); + + stf_status ret = ikev2_process_sa_payload("IKE initiator (accepting)", + &sa_pd->pbs, + /*expect_ike*/ TRUE, + /*expect_spi*/ FALSE, + /*expect_accepted*/ TRUE, + LIN(POLICY_OPPORTUNISTIC, c->policy), + &st->st_accepted_ike_proposal, + ike_proposals, ike->sa.st_logger); + if (ret != STF_OK) { + dbg("ikev2_parse_parent_sa_body() failed in ikev2_parent_inR1outI2()"); + return ret; /* initiator; no response */ + } } if (!ikev2_proposal_to_trans_attrs(st->st_accepted_ike_proposal, @@ -1552,6 +1618,7 @@ stf_status ikev2_parent_inR1outI2(struct ike_sa *ike, */ return STF_FAIL; } + } /* @@ -1592,10 +1659,15 @@ stf_status ikev2_parent_inR1outI2(struct ike_sa *ike, .initiator = ike->sa.st_ike_spis.initiator, .responder = md->hdr.isa_ike_responder_spi, }; - start_dh_v2(st, "ikev2_inR1outI2 KE", - SA_INITIATOR, - NULL, NULL, &st->st_ike_rekey_spis, - ikev2_parent_inR1outI2_continue); + + if(ike->sa.st_resuming) { + ikev2_parent_inR1outI2_continue(st, md, NULL); + } else { + start_dh_v2(st, "ikev2_inR1outI2 KE", + SA_INITIATOR, + NULL, NULL, &st->st_ike_rekey_spis, + ikev2_parent_inR1outI2_continue); + } return STF_SUSPEND; } @@ -1813,13 +1885,21 @@ static stf_status ikev2_parent_inR1outI2_tail(struct state *pst, struct msg_dige struct connection *const pc = pst->st_connection; /* parent connection */ struct ike_sa *ike = pexpect_ike_sa(pst); - if (!finish_dh_v2(pst, r, FALSE)) { - /* - * XXX: this is the initiator so returning a - * notification is kind of useless. - */ - pstat_sa_failed(pst, REASON_CRYPTO_FAILED); - return STF_FAIL + v2N_INVALID_SYNTAX; /* STF_FATAL? */ + if (ike->sa.st_resuming) { + PK11SymKey *shared = symkey_from_hunk("sk_d_old", pst->st_connection->temp_vars.ticket_variables.sk_d_old, + ike->sa.st_logger); + if (!skeyseed_v2_sr(pst, shared, ike->sa.st_logger)){ + return STF_FAIL; + } + } else { + if (!finish_dh_v2(pst, r, FALSE)) { + /* + * XXX: this is the initiator so returning a + * notification is kind of useless. + */ + pstat_sa_failed(pst, REASON_CRYPTO_FAILED); + return STF_FAIL + v2N_INVALID_SYNTAX; /* STF_FATAL? */ + } } /* @@ -1893,23 +1973,31 @@ static stf_status ikev2_parent_inR1outI2_tail(struct state *pst, struct msg_dige { shunk_t data; ike->sa.st_v2_id_payload.header = build_v2_id_payload(&pc->spd.this, &data, - "my IDi", ike->sa.st_logger); + "my IDi", ike->sa.st_logger); ike->sa.st_v2_id_payload.data = clone_hunk(data, "my IDi"); } - ike->sa.st_v2_id_payload.mac = v2_hash_id_payload("IDi", ike, - "st_skey_pi_nss", - ike->sa.st_skey_pi_nss); - if (pst->st_seen_ppk && !LIN(POLICY_PPK_INSIST, pc->policy)) { - /* ID payload that we've build is the same */ - ike->sa.st_v2_id_payload.mac_no_ppk_auth = - v2_hash_id_payload("IDi (no-PPK)", ike, - "sk_pi_no_pkk", - ike->sa.st_sk_pi_no_ppk); + if (!ike->sa.st_resuming) { + ike->sa.st_v2_id_payload.mac = v2_hash_id_payload("IDi", ike, + "st_skey_pi_nss", + ike->sa.st_skey_pi_nss); + if (pst->st_seen_ppk && !LIN(POLICY_PPK_INSIST, pc->policy)) { + /* ID payload that we've build is the same */ + ike->sa.st_v2_id_payload.mac_no_ppk_auth = + v2_hash_id_payload("IDi (no-PPK)", ike, + "sk_pi_no_pkk", + ike->sa.st_sk_pi_no_ppk); + } } { enum keyword_authby authby = v2_auth_by(ike); + if (ike->sa.st_resuming) { + if (authby != pc->temp_vars.ticket_variables.sr_auth_method) { + dbg("refusing resumption, auth method mismatched"); + return STF_FATAL; + } + } enum ikev2_auth_method auth_method = v2_auth_method(ike, authby); switch (auth_method) { case IKEv2_AUTH_RSA: @@ -2125,7 +2213,7 @@ static stf_status ikev2_parent_inR1outI2_auth_signature_continue(struct ike_sa * } /* send [CERT,] payload RFC 4306 3.6, 1.2) */ - if (send_cert) { + if (send_cert && !ike->sa.st_resuming) { stf_status certstat = ikev2_send_cert(cst, &sk.pbs); if (certstat != STF_OK) return certstat; @@ -2342,6 +2430,13 @@ static stf_status ikev2_parent_inR1outI2_auth_signature_continue(struct ike_sa * free_chunk_content(&null_auth); } + /* Notification payload for ticket request */ + if (LIN(POLICY_SESSION_RESUME, cc->policy)) { + if (!emit_v2N(v2N_TICKET_REQUEST, &sk.pbs)) { + return STF_INTERNAL_ERROR; + } + } + /* send CP payloads */ if (pc->modecfg_domains != NULL || pc->modecfg_dns != NULL) { /* @@ -2462,7 +2557,7 @@ static crypto_req_cont_func ikev2_ike_sa_process_auth_request_no_skeyid_continue stf_status ikev2_ike_sa_process_auth_request_no_skeyid(struct ike_sa *ike, struct child_sa *child, - struct msg_digest *md UNUSED) + struct msg_digest *md) { pexpect(child == NULL); struct state *st = &ike->sa; @@ -2474,18 +2569,22 @@ stf_status ikev2_ike_sa_process_auth_request_no_skeyid(struct ike_sa *ike, dbg("ikev2 parent inI2outR2: calculating g^{xy} in order to decrypt I2"); - /* initiate calculation of g^xy */ - start_dh_v2(st, "ikev2_inI2outR2 KE", - SA_RESPONDER, - NULL, NULL, &st->st_ike_spis, - ikev2_ike_sa_process_auth_request_no_skeyid_continue); + if (ike->sa.st_resuming) { + ikev2_ike_sa_process_auth_request_no_skeyid_continue(st, md, NULL); + } else { + /* initiate calculation of g^xy */ + start_dh_v2(st, "ikev2_inI2outR2 KE", + SA_RESPONDER, + NULL, NULL, &st->st_ike_spis, + ikev2_ike_sa_process_auth_request_no_skeyid_continue); + } return STF_SUSPEND; } static void ikev2_ike_sa_process_auth_request_no_skeyid_continue(struct state *st, struct msg_digest *md, struct pluto_crypto_req *r) -{ +{ dbg("%s() for #%lu %s: calculating g^{xy}, sending R2", __func__, st->st_serialno, st->st_state->name); @@ -2499,19 +2598,33 @@ static void ikev2_ike_sa_process_auth_request_no_skeyid_continue(struct state *s /* extract calculated values from r */ - if (!finish_dh_v2(st, r, FALSE)) { - /* - * Since dh failed, the channel isn't end-to-end - * encrypted. Send back a clear text notify and then - * abandon the connection. - */ - dbg("aborting IKE SA: DH failed"); - send_v2N_response_from_md(md, v2N_INVALID_SYNTAX, NULL); - /* replace (*mdp)->st with st ... */ - complete_v2_state_transition(md->st, md, STF_FATAL); - return; - } + if (ike->sa.st_resuming) { + chunk_t temp = alloc_chunk(sizeof(st->st_sk_d_old), "temp"); + memcpy(temp.ptr, &st->st_sk_d_old, MAX_SK_d_LEN); + PK11SymKey *shared = symkey_from_hunk("sk_d_old", temp, st->st_logger); + free_chunk_content(&temp); + if (!skeyseed_v2_sr(st, shared, st->st_logger)){ + dbg("aborting Session Resume"); + send_v2N_response_from_md(md, v2N_INVALID_SYNTAX, NULL); + complete_v2_state_transition(md->st, md, STF_FATAL); + return; + } + } else { + if (!finish_dh_v2(st, r, FALSE)) { + /* + * Since dh failed, the channel isn't end-to-end + * encrypted. Send back a clear text notify and then + * abandon the connection. + */ + dbg("aborting IKE SA: DH failed"); + send_v2N_response_from_md(md, v2N_INVALID_SYNTAX, NULL); + /* replace (*mdp)->st with st ... */ + complete_v2_state_transition(md->st, md, STF_FATAL); + return; + } + } + ikev2_process_state_packet(pexpect_ike_sa(st), st, md); } @@ -2708,6 +2821,10 @@ stf_status ikev2_parent_inI2outR2_id_tail(struct msg_digest *md) } } st->st_seen_initialc = md->pbs[PBS_v2N_INITIAL_CONTACT] != NULL; + if (md->pbs[PBS_v2N_TICKET_REQUEST] != NULL) { + dbg("received v2N_TICKET_REQUEST"); + st->st_seen_ticket_request = true; + } /* * If we found proper PPK ID and policy allows PPK, use that. @@ -3119,6 +3236,19 @@ static stf_status ikev2_parent_inI2outR2_auth_signature_continue(struct ike_sa * return STF_INTERNAL_ERROR; } + if (st->st_seen_ticket_request) { + if (LIN(POLICY_SESSION_RESUME, c->policy)) { + if (!emit_ticket_lt_opaque_notification(st, &sk.pbs)) { + return STF_INTERNAL_ERROR; + } + } + else { + if (!emit_v2N(v2N_TICKET_NACK, &sk.pbs)) { + return STF_INTERNAL_ERROR; + } + } + } + /* send out the IDr payload */ { @@ -3567,6 +3697,48 @@ stf_status ikev2_parent_inR2(struct ike_sa *ike, struct child_sa *child, struct } st->st_seen_no_tfc = md->pbs[PBS_v2N_ESP_TFC_PADDING_NOT_SUPPORTED] != NULL; /* Technically, this should be only on the child state */ + if (md->pbs[PBS_v2N_TICKET_NACK] != NULL) { + dbg("received v2N_TICKET_NACK"); + st->st_seen_ticket_nack = pst->st_seen_ticket_nack = true; + } + if (md->pbs[PBS_v2N_TICKET_ACK] != NULL) { + dbg("received v2N_TICKET_ACK"); + st->st_seen_ticket_ack = pst->st_seen_ticket_ack = true; + } + if (md->pbs[PBS_v2N_TICKET_LT_OPAQUE] != NULL) { + dbg("received v2N_TICKET_LT_OPAQUE") + pb_stream pbs = *md->pbs[PBS_v2N_TICKET_LT_OPAQUE]; + + struct ikev2_ticket_lifetime tl; + if (!pbs_in_struct(&pbs, &tl, sizeof(tl), + &ikev2_ticket_lt_desc, NULL, ike->sa.st_logger)) { + dbg("received malformed TICKET_LT_OPAQUE payload") + return STF_FATAL; + } + + size_t len = pbs_left(&pbs); + pst->st_connection->temp_vars.ticket_variables.stored_ticket = alloc_chunk(len, "Ticket"); + + if (!pbs_in_raw(&pbs, pst->st_connection->temp_vars.ticket_variables.stored_ticket.ptr, len, + "ticket", ike->sa.st_logger)) { + dbg("error while extracting ticket(encrypted) from variable part of IKEv2_TICKET_LT_OPAQUE Notify payload") + free_chunk_content(&pst->st_connection->temp_vars.ticket_variables.stored_ticket); + return STF_FATAL; + } + + pst->st_connection->temp_vars.ticket_variables.sr_serialco = pst->st_connection->serial_from.co; + str_id(&pst->st_connection->spd.that.id, &pst->st_connection->temp_vars.ticket_variables.peer_id); + pst->st_connection->temp_vars.ticket_variables.sk_d_old = chunk_from_symkey("sk_d_old", pst->st_skey_d_nss, pst->st_logger); + + pst->st_connection->temp_vars.ticket_variables.sr_encr = pst->st_oakley.ta_encrypt->common.id[IKEv2_ALG_ID]; + pst->st_connection->temp_vars.ticket_variables.sr_prf = pst->st_oakley.ta_prf->common.id[IKEv2_ALG_ID]; + pst->st_connection->temp_vars.ticket_variables.sr_dh = pst->st_oakley.ta_dh->common.id[IKEv2_ALG_ID]; + pst->st_connection->temp_vars.ticket_variables.sr_integ = pst->st_oakley.ta_integ->common.id[IKEv2_ALG_ID]; + pst->st_connection->temp_vars.ticket_variables.sr_enc_keylen = pst->st_oakley.enckeylen; + pst->st_connection->temp_vars.ticket_variables.sr_auth_method = pst->st_connection->spd.that.authby; + } + + /* * On the initiator, we can STF_FATAL on IKE SA errors, because no * packet needs to be sent anymore. And we cannot recover. Unlike diff --git a/programs/pluto/ikev2_prf.c b/programs/pluto/ikev2_prf.c index 3adb9af55d2..b3840aa41ee 100644 --- a/programs/pluto/ikev2_prf.c +++ b/programs/pluto/ikev2_prf.c @@ -71,6 +71,19 @@ PK11SymKey *ikev2_ike_sa_rekey_skeyseed(const struct prf_desc *prf_desc, Ni, Nr, logger); } +/* + * SKEYSEED = prf(SK_d (old), "Resumption" | Ni | Nr) + */ +PK11SymKey *ikev2_ike_sa_session_resume_skeyseed(const struct prf_desc *prf_desc, + PK11SymKey *SK_d_old, + const chunk_t Ni, const chunk_t Nr, + struct logger *logger) +{ + return prf_desc->prf_ikev2_ops->ike_sa_session_resume_skeyseed(prf_desc, + SK_d_old, + Ni, Nr, logger); +} + /* * Compute: prf+ (SKEYSEED, Ni | Nr | SPIi | SPIr) */ diff --git a/programs/pluto/ikev2_prf.h b/programs/pluto/ikev2_prf.h index 8a6dcef7658..942406be10c 100644 --- a/programs/pluto/ikev2_prf.h +++ b/programs/pluto/ikev2_prf.h @@ -46,6 +46,11 @@ PK11SymKey *ikev2_ike_sa_rekey_skeyseed(const struct prf_desc *prf_desc, const chunk_t Ni, const chunk_t Nr, struct logger *logger); +PK11SymKey *ikev2_ike_sa_session_resume_skeyseed(const struct prf_desc *prf_desc, + PK11SymKey *old_SK_d, + const chunk_t Ni, const chunk_t Nr, + struct logger *logger); + PK11SymKey *ikev2_ike_sa_keymat(const struct prf_desc *prf_desc, PK11SymKey *skeyseed, const chunk_t Ni, const chunk_t Nr, diff --git a/programs/pluto/ikev2_psk.c b/programs/pluto/ikev2_psk.c index 4551f258615..097ed3e6c2f 100644 --- a/programs/pluto/ikev2_psk.c +++ b/programs/pluto/ikev2_psk.c @@ -184,7 +184,9 @@ static struct crypt_mac ikev2_calculate_psk_sighash(bool verify, * RFC 4306 2.15: * AUTH = prf(prf(Shared Secret, "Key Pad for IKEv2"), ) */ - passert(idhash->len == ike->sa.st_oakley.ta_prf->prf_output_size); + if (ike->sa.st_connection->temp_vars.ticket_variables.stored_ticket.len == 0) { + passert(idhash->len == ike->sa.st_oakley.ta_prf->prf_output_size); + } return ikev2_psk_auth(ike->sa.st_oakley.ta_prf, *pss, firstpacket, *nonce, idhash, ike->sa.st_logger); } diff --git a/programs/pluto/ikev2_redirect.c b/programs/pluto/ikev2_redirect.c index b96b6e51a50..31497ddb851 100644 --- a/programs/pluto/ikev2_redirect.c +++ b/programs/pluto/ikev2_redirect.c @@ -551,6 +551,18 @@ void send_active_redirect_in_informational(struct state *st) sensitive_ipstr(&st->st_remote_endpoint, &b)); } +stf_status process_IKE_SESSION_RESUME_v2N_REDIRECT_response(struct ike_sa *ike, + struct child_sa *child, + struct msg_digest *md) +{ + /* dropping the ticket */ + free_chunk_content(&ike->sa.st_connection->temp_vars.ticket_variables.stored_ticket); + + stf_status e = process_IKE_SA_INIT_v2N_REDIRECT_response(ike, child, md) ? STF_OK : STF_INTERNAL_ERROR; + complete_v2_state_transition(md->st, md, e); + return STF_SUSPEND; +} + stf_status process_IKE_SA_INIT_v2N_REDIRECT_response(struct ike_sa *ike, struct child_sa *child, struct msg_digest *md) diff --git a/programs/pluto/ikev2_redirect.h b/programs/pluto/ikev2_redirect.h index a95df4bddb2..53e195da2be 100644 --- a/programs/pluto/ikev2_redirect.h +++ b/programs/pluto/ikev2_redirect.h @@ -114,4 +114,8 @@ extern stf_status process_IKE_SA_INIT_v2N_REDIRECT_response(struct ike_sa *ike, struct child_sa *child, struct msg_digest *md); +extern stf_status process_IKE_SESSION_RESUME_v2N_REDIRECT_response(struct ike_sa *ike, + struct child_sa *child, + struct msg_digest *md); + #endif diff --git a/programs/pluto/ikev2_resume.c b/programs/pluto/ikev2_resume.c new file mode 100644 index 00000000000..5277b4bea32 --- /dev/null +++ b/programs/pluto/ikev2_resume.c @@ -0,0 +1,212 @@ +/* IKEv2 Session Resumption RFC 5723 + * + * Copyright (C) 2020 Nupur Agrawal + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "defs.h" +#include "state.h" +#include "packet.h" +#include "deltatime.h" +#include "id.h" +#include "chunk.h" +#include "log.h" +#include "ikev2.h" +#include "ikev2_resume.h" +#include "crypt_symkey.h" +#include "ikev2_send.h" +#include "timer.h" +#include "pluto_crypt.h" +#include "ipsec_doi.h" +#include "ikev2_message.h" +#include "ikev1.h" +#include "ikev1_send.h" +#include "demux.h" +#include "vendor.h" +#include "pending.h" +#include "nat_traversal.h" +#include "pluto_x509.h" + +chunk_t st_to_ticket(const struct state *st) +{ + struct ticket_by_val tkt; + + tkt.sr_serialco = st->st_connection->serial_from.co; + str_id(&st->st_connection->spd.that.id, &tkt.peer_id); + + /* old skeyseed */ + chunk_t sk = chunk_from_symkey("sk_d_old", st->st_skey_d_nss, st->st_logger); + passert(sk.len <= MAX_SK_d_LEN); + memcpy(&tkt.sk_d_old, sk.ptr, sk.len); + free_chunk_content(&sk); + + /*Algorithm description*/ + tkt.sr_encr = st->st_oakley.ta_encrypt->common.id[IKEv2_ALG_ID]; + tkt.sr_prf = st->st_oakley.ta_prf->common.id[IKEv2_ALG_ID]; + tkt.sr_integ = st->st_oakley.ta_integ->common.id[IKEv2_ALG_ID]; + tkt.sr_dh = st->st_oakley.ta_dh->common.id[IKEv2_ALG_ID]; + tkt.sr_enc_keylen = st->st_oakley.enckeylen; + tkt.sr_auth_method = st->st_connection->spd.that.authby; + + /* caller is responsible for freeing this */ + return clone_bytes_as_chunk(&tkt, sizeof(struct ticket_by_val), "IKEv2 ticket_by_val"); +} + +/* + * builds notificaton payload data for ticket_lt_opaque + */ +static chunk_t build_resume_notification(struct state *st, struct logger *logger) +{ + struct ikev2_ticket_lifetime tl; + + /* + * RFC 5723 Section 6.2 + * The lifetime of the ticket sent by the gateway SHOULD be the minimum + * of the IKE SA lifetime (per the gateway's local policy) and its re- + * authentication time. + */ + tl.sr_lifetime = deltasecs(st->st_connection->sa_ike_life_seconds); + + chunk_t ticket = st_to_ticket(st); + + /* + * Dummy pbs we need for more elegant notification + * data construction (using out_struct and et. al.) + */ + uint8_t buf[MIN_OUTPUT_UDP_SIZE]; + struct pbs_out resume_pbs = open_pbs_out("entire resume ticket", + buf, sizeof(buf), logger); + + if (!out_struct(&tl, &ikev2_ticket_lt_desc, &resume_pbs, NULL)) + return empty_chunk; + + if (!out_raw(ticket.ptr, ticket.len , &resume_pbs, "resume (encrypted) ticket data")) + return empty_chunk; + + close_output_pbs(&resume_pbs); + free_chunk_content(&ticket); + + /* please make sure callee frees this chunk */ + return clone_out_pbs_as_chunk(&resume_pbs, "redirect notify data"); +} + +bool emit_ticket_lt_opaque_notification(struct state *st, pb_stream *pbs) +{ + chunk_t data = build_resume_notification(st, pbs->out_logger); + + if (data.len == 0) { + libreswan_log("failed to build session resumption ticket - skipping notify payload"); + return false; + } + + bool ret = emit_v2N_bytes(v2N_TICKET_LT_OPAQUE, data.ptr, data.len, pbs); + free_chunk_content(&data); + return ret; +} + +bool emit_ticket_opaque_notification(chunk_t ticket, pb_stream *pbs) +{ + if (ticket.len == 0) { + libreswan_log("failed to find session resumption ticket - skipping notify payload"); + return false; + } + + bool ret = emit_v2N_bytes(v2N_TICKET_OPAQUE, ticket.ptr, ticket.len, pbs); + return ret; +} + +bool decrypt_ticket(pb_stream *pbs, size_t len, struct ike_sa *ike) +{ + passert(sizeof(struct ticket_by_val) == len); + + struct ticket_by_val temp; + if (!in_raw(&temp, len, pbs, "ticket")){ + return false; + } + + memcpy(ike->sa.st_sk_d_old, temp.sk_d_old, MAX_SK_d_LEN); + if (!set_ikev2_accepted_proposal(ike, temp.sr_enc_keylen, temp.sr_encr, temp.sr_prf, temp.sr_integ, temp.sr_dh)) { + return false; + } + return true; +} + +/* + * Note: This is called on whack command ipsec whack --suspend --name + */ +void suspend_connection(struct connection *c) +{ + libreswan_log("suspending connection '%s' - deleting states", c->name); + /* terminate connection, but if an instance, don't delete ourselves */ + c->policy &= ~POLICY_UP; + delete_states_by_connection(c, false, NULL); +} + +/* + * + *************************************************************** + * SESSION_RESUME_PARENT_OUTI1 ***** + *************************************************************** + * + * + * Initiate an Oakley Main Mode exchange. + * HDR, N(TICKET_OPAQUE), Ni --> + * + * Note: this is not called from demux.c, but from ipsecdoi_initiate(), + * if initiator possesses ticket. + * + */ + +static void ikev2_session_resume_outI1_continue(struct state *st, struct msg_digest *md, + struct pluto_crypto_req *r); + +void ikev2_session_resume_outI1(struct fd *whack_sock, + struct connection *c, + struct state *predecessor UNUSED, + lset_t policy, + unsigned long try, + const threadtime_t *inception UNUSED, + struct xfrm_user_sec_ctx_ike *uctx UNUSED) +{ + const struct finite_state *fs = finite_states[STATE_PARENT_RESUME_I0]; + pexpect(fs->nr_transitions == 1); + const struct state_v2_microcode *transition = &fs->v2_transitions[0]; + struct ike_sa *ike = new_v2_ike_state(transition, SA_INITIATOR, + ike_initiator_spi(), zero_ike_spi, + c, policy, try, whack_sock); + + push_cur_state(&ike->sa); + /* set up new state */ + struct state *st = &ike->sa; + passert(st->st_ike_version == IKEv2); + passert(st->st_state->kind == STATE_PARENT_RESUME_I0); + passert(st->st_sa_role == SA_INITIATOR); + st->st_try = try; + + request_nonce("Session Resume Initiator Nonce Ni", st, ikev2_session_resume_outI1_continue); +} + +void ikev2_session_resume_outI1_continue(struct state *st, struct msg_digest *md, + struct pluto_crypto_req *r) +{ + dbg("%s() for #%lu %s", + __func__, st->st_serialno, st->st_state->name); + pexpect(md == NULL); + + struct ike_sa *ike = pexpect_ike_sa(st); + pexpect(ike->sa.st_sa_role == SA_INITIATOR); + pexpect(st->st_state->kind == STATE_PARENT_RESUME_I0); + + unpack_nonce(&st->st_ni, r); + stf_status e = record_v2_IKE_SA_INIT_OR_RESUME_request(ike) ? STF_OK : STF_INTERNAL_ERROR; + complete_v2_state_transition(st, NULL, e); +} diff --git a/programs/pluto/ikev2_resume.h b/programs/pluto/ikev2_resume.h new file mode 100644 index 00000000000..7e7c3b1a238 --- /dev/null +++ b/programs/pluto/ikev2_resume.h @@ -0,0 +1,70 @@ +#ifndef IKEV2_RESUME_H +#define IKEV2_RESUME_H + +#include + +#include "state.h" +#include "packet.h" +#include "deltatime.h" +#include "connections.h" + +/* made up - fixup to actual known max later */ +#define MAX_SK_d_LEN 256 + +struct ticket_by_val { + + unsigned long sr_serialco; + id_buf peer_id; + + /* Reference to sk_d_old */ + char sk_d_old[MAX_SK_d_LEN]; + + /* All the chosen Algorithm Description */ + int sr_encr; + int sr_prf; + int sr_integ; + int sr_dh; + int sr_enc_keylen; + + enum keyword_authby sr_auth_method; + + deltatime_t sr_expire; + +}; + +/* + * forms a ticket chunk. + * assign state's member varibles to ticket's + */ +chunk_t st_to_ticket(const struct state *st); + +/* + * Emit IKEv2 Notify TICKET_LT_OPAQUE payload. + * + * @param *st struct state + * @param pbs output stream + */ +extern bool emit_ticket_lt_opaque_notification(struct state *st, pb_stream *pbs); + +/* + * Emit IKEv2 Notify TICKET_OPAQUE payload. + * + * @param *ticket chunk_t stored encrypted ticket chunk at the client side + * @param pbs output stream + */ +extern bool emit_ticket_opaque_notification(chunk_t ticket, pb_stream *pbs); + +void suspend_connection(struct connection *c); +bool set_ikev2_accepted_proposal(struct ike_sa *ike, int enc_keylen, + int encr, int prf, int integ, int dh); +bool decrypt_ticket(pb_stream *pbs, size_t len, struct ike_sa *ike); + +extern void ikev2_session_resume_outI1(struct fd *whack_sock, + struct connection *c, + struct state *predecessor UNUSED, + lset_t policy, + unsigned long try, + const threadtime_t *inception UNUSED, + struct xfrm_user_sec_ctx_ike *uctx UNUSED); + +#endif diff --git a/programs/pluto/ikev2_spdb_struct.c b/programs/pluto/ikev2_spdb_struct.c index 156ab69ca28..305d79d8158 100644 --- a/programs/pluto/ikev2_spdb_struct.c +++ b/programs/pluto/ikev2_spdb_struct.c @@ -58,6 +58,7 @@ #include "rnd.h" #include "ikev2_message.h" /* for build_ikev2_critical() */ #include "nat_traversal.h" +#include "ikev2_resume.h" /* * Two possible attribute formats (fixed and variable). In IKEv2 the @@ -2328,3 +2329,29 @@ void ikev2_copy_cookie_from_sa(const struct ikev2_proposal *accepted_ike_proposa /* st_icookie is an array of len COOKIE_SIZE. only accept this length */ memcpy(&cookie->bytes, accepted_ike_proposal->remote_spi.bytes, COOKIE_SIZE); } + +bool set_ikev2_accepted_proposal(struct ike_sa *ike, int enc_keylen, + int encr, int prf, int integ, int dh) +{ + passert(ike->sa.st_accepted_ike_proposal == NULL); + struct ikev2_proposal *temp_proposal = alloc_thing(struct ikev2_proposal, "temp proposal"); + + temp_proposal->propnum = 0; + temp_proposal->protoid = 1; + temp_proposal->remote_spi.size = 0; + temp_proposal->transforms[IKEv2_TRANS_TYPE_ENCR].transform->id = encr; + temp_proposal->transforms[IKEv2_TRANS_TYPE_ENCR].transform->attr_keylen = enc_keylen; + temp_proposal->transforms[IKEv2_TRANS_TYPE_PRF].transform->id = prf; + temp_proposal->transforms[IKEv2_TRANS_TYPE_INTEG].transform->id = integ; + temp_proposal->transforms[IKEv2_TRANS_TYPE_DH].transform->id = dh; + + for(int i=1; i<5; i++) { + temp_proposal->transforms[i].transform->valid = true; + } + + /* transfer ownership of TEMP_PROPOSAL to caller */ + ike->sa.st_accepted_ike_proposal = temp_proposal; + temp_proposal = NULL; + + return true; +} diff --git a/programs/pluto/ikev2_states.c b/programs/pluto/ikev2_states.c index 8c43a7dec0c..abe78548f45 100644 --- a/programs/pluto/ikev2_states.c +++ b/programs/pluto/ikev2_states.c @@ -62,6 +62,7 @@ struct finite_state v2_states[] = { */ S(STATE_PARENT_I0, "waiting for KE to finish", CAT_IGNORE), + S(STATE_PARENT_RESUME_I0, "resuming", CAT_IGNORE), /* * Count I1 as half-open too because with ondemand, a diff --git a/programs/pluto/ipsec_doi.c b/programs/pluto/ipsec_doi.c index e7d688b5121..2c38d91a0a1 100644 --- a/programs/pluto/ipsec_doi.c +++ b/programs/pluto/ipsec_doi.c @@ -84,6 +84,7 @@ #include "pending.h" #include "iface.h" #include "ikev2_delete.h" /* for record_v2_delete(); but call is dying */ +#include "ikev2_resume.h" /* * Process KE values. @@ -188,7 +189,11 @@ static initiator_function *pick_initiator(struct connection *c, lset_t policy) { if (policy & c->policy & POLICY_IKEV2_ALLOW) { - return ikev2_parent_outI1; + if (c->temp_vars.ticket_variables.stored_ticket.len > 0) { + return ikev2_session_resume_outI1; + } else { + return ikev2_parent_outI1; + } } else { /* we may try V1; Aggressive or Main Mode? */ return (policy & POLICY_AGGRESSIVE) ? aggr_outI1 : main_outI1; diff --git a/programs/pluto/packet.c b/programs/pluto/packet.c index 1f408b60d39..15b26615a00 100644 --- a/programs/pluto/packet.c +++ b/programs/pluto/packet.c @@ -1568,6 +1568,27 @@ struct_desc suggested_group_desc = { .pt = ISAKMP_NEXT_v2NONE, }; +/* + * TICKET_LT_OPAQUE Notify Payload - variable part + * + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Lifetime | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * ~ Ticket ~ + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ +static field_desc ikev2_ticket_fields[] = { + { ft_nat, 32 / BITS_PER_BYTE, "Lifetime", NULL }, + { ft_end, 0, NULL, NULL } +}; +struct_desc ikev2_ticket_lt_desc = { + .name = "TICKET_LT_OPAQUE Notify Data", + .fields = ikev2_ticket_fields, + .size = sizeof(struct ikev2_ticket_lifetime), +}; + /* * IPcomp Notify as per RFC 7296: * diff --git a/programs/pluto/packet.h b/programs/pluto/packet.h index f0facb6ef60..4645b31815d 100644 --- a/programs/pluto/packet.h +++ b/programs/pluto/packet.h @@ -1142,7 +1142,6 @@ extern struct_desc ikev2_skf_desc; extern struct_desc ikev2_vendor_id_desc; - /* union of all payloads */ union payload { @@ -1193,6 +1192,11 @@ struct ikev2_notify_ipcomp_data { }; extern struct_desc ikev2notify_ipcomp_data_desc; +struct ikev2_ticket_lifetime { + uint32_t sr_lifetime; +}; +extern struct_desc ikev2_ticket_lt_desc; + extern struct_desc sec_ctx_desc; /* diff --git a/programs/pluto/pluto_constants.c b/programs/pluto/pluto_constants.c index 85526ea58f8..eff0f2ed8ff 100644 --- a/programs/pluto/pluto_constants.c +++ b/programs/pluto/pluto_constants.c @@ -256,6 +256,7 @@ const char *const sa_policy_bit_names[] = { "ESN_NO", "ESN_YES", "RSASIG_v1_5", + "SESSION_RESUME", NULL /* end for bitnamesof() */ }; diff --git a/programs/pluto/pluto_crypt.h b/programs/pluto/pluto_crypt.h index 564ca2e8e0b..90d74336adb 100644 --- a/programs/pluto/pluto_crypt.h +++ b/programs/pluto/pluto_crypt.h @@ -399,6 +399,8 @@ extern void start_dh_v2(struct state *st, extern bool finish_dh_v2(struct state *st, struct pluto_crypto_req *r, bool only_shared); +extern bool skeyseed_v2_sr(struct state *st, PK11SymKey *sk_d_old, struct logger *logger); + /* internal */ extern void calc_dh_v2(struct pluto_crypto_req *r, struct logger *logger); extern void cancelled_dh_v2(struct pcr_dh_v2 *dh); diff --git a/programs/pluto/rcv_whack.c b/programs/pluto/rcv_whack.c index fc359c43dae..02446c90348 100644 --- a/programs/pluto/rcv_whack.c +++ b/programs/pluto/rcv_whack.c @@ -79,6 +79,7 @@ #include "initiate.h" #include "iface.h" #include "show.h" +#include "ikev2_resume.h" #ifdef HAVE_SECCOMP #include "pluto_seccomp.h" #endif @@ -642,6 +643,28 @@ static bool whack_process(const struct whack_message *const m, struct show *s) } } + if (m->whack_suspend) { + if(m->name == NULL) { + whack_log(RC_FATAL, whackfd, + "received whack command to suspend a connection, but did not receive the connection name - ignored"); + } else { + struct connection *c = conn_by_name(m->name, FALSE); + if (c == NULL) { + whack_log(RC_UNKNOWN_NAME, whackfd, + "Connection with given name not found, try again with valid name"); + } else { + if (c->temp_vars.ticket_variables.stored_ticket.len == 0) { + whack_log(RC_FATAL, whackfd, + "no stored ticket, cannot suspend connection"); + } + else + { + suspend_connection(c); + } + } + } + } + if (m->whack_oppo_initiate) { if (!listening) { whack_log(RC_DEAF, whackfd, diff --git a/programs/pluto/state.h b/programs/pluto/state.h index 653ec0c0648..f33fc10bbe0 100644 --- a/programs/pluto/state.h +++ b/programs/pluto/state.h @@ -384,6 +384,8 @@ struct state { ip_endpoint st_remote_endpoint; /* where to send packets to */ + char st_sk_d_old[256]; /* store the sk_d_ol from session resumption ticket */ + /* * dhr 2013: why [.st_interface]? There was already * connection->interface @@ -450,6 +452,7 @@ struct state { struct v2_outgoing_fragment *st_v2_outgoing[MESSAGE_ROLE_ROOF]; struct v2_incomming_fragments *st_v2_incomming[MESSAGE_ROLE_ROOF]; + bool st_resuming; /* Session Resumption */ bool st_viable_parent; /* can initiate new CERAET_CHILD_SA */ struct ikev2_proposal *st_accepted_ike_proposal; struct ikev2_proposal *st_accepted_esp_or_ah_proposal; @@ -763,6 +766,10 @@ struct state { bool st_seen_redirect_sup; /* did we receive IKEv2_REDIRECT_SUPPORTED */ bool st_sent_redirect; /* did we send IKEv2_REDIRECT in IKE_AUTH (response) */ bool st_redirected_in_auth; /* were we redirected in IKE_AUTH */ + bool st_seen_ticket_request; /* did we receive session ticket request */ + bool st_seen_ticket_ack; /* did we receive session ticket ack */ + bool st_seen_ticket_nack; /* did we receive session ticket nack */ + chunk_t stored_ticket; /* ticket stored by client */ generalName_t *st_requested_ca; /* collected certificate requests */ uint8_t st_reply_xchg; bool st_peer_wants_null; /* We received IDr payload of type ID_NULL (and we allow POLICY_AUTH_NULL */ diff --git a/programs/whack/whack.c b/programs/whack/whack.c index b0d0973e68d..df61cf83b67 100644 --- a/programs/whack/whack.c +++ b/programs/whack/whack.c @@ -115,6 +115,7 @@ static void help(void) " [--ikefrag-allow | --ikefrag-force] [--no-ikepad] \\\n" " [--esn ] [--no-esn] [--decap-dscp] [--nopmtudisc] [--mobike] \\\n" " [--tcp ] --tcp-remote-port \\\n" + " [--tcp ] [--no-udp] [--session-resumption] \\\n" #ifdef HAVE_NM " [--nm-configured] \\\n" #endif @@ -199,6 +200,8 @@ static void help(void) "\n" "refresh dns: whack --ddns\n" "\n" + "suspend: whack --suspend --name \n" + "\n" #ifdef HAVE_SECCOMP "testing: whack --seccomp-crashtest (CAREFUL!)\n" "\n" @@ -350,6 +353,8 @@ enum option_enums { OPT_USERNAME, OPT_XAUTHPASS, + OPT_SESSION_SUSPEND, + #define OPT_LAST2 OPT_XAUTHPASS /* last "normal" option, range 2 */ /* List options */ @@ -449,6 +454,7 @@ enum option_enums { CD_CISCO_UNITY, CD_FAKE_STRONGSWAN, CD_MOBIKE, + CD_SESSION_RESUME, CD_IKE, CD_IKE_TCP, CD_IKE_TCP_REMOTE_PORT, @@ -605,6 +611,7 @@ static const struct option long_opts[] = { { "oppodport", required_argument, NULL, OPT_OPPO_DPORT + OO }, { "asynchronous", no_argument, NULL, OPT_ASYNC + OO }, + { "suspend", no_argument, NULL, OPT_SESSION_SUSPEND + OO }, { "rekey-ike", no_argument, NULL, OPT_REKEY_IKE + OO }, { "rekey-ipsec", no_argument, NULL, OPT_REKEY_IPSEC + OO }, @@ -703,6 +710,7 @@ static const struct option long_opts[] = { { "cisco-unity", no_argument, NULL, CD_CISCO_UNITY + OO }, { "fake-strongswan", no_argument, NULL, CD_FAKE_STRONGSWAN + OO }, PS("mobike", MOBIKE), + PS("session-resumption", SESSION_RESUME), { "dpddelay", required_argument, NULL, CD_DPDDELAY + OO + NUMERIC_ARG }, { "dpdtimeout", required_argument, NULL, CD_DPDTIMEOUT + OO + NUMERIC_ARG }, @@ -1280,6 +1288,10 @@ int main(int argc, char **argv) msg.whack_rekey_ipsec = TRUE; continue; + case OPT_SESSION_SUSPEND: /* --suspend */ + msg.whack_suspend = TRUE; + continue; + case OPT_DELETE: /* --delete */ msg.whack_delete = TRUE; continue; @@ -1716,6 +1728,9 @@ int main(int argc, char **argv) /* --mobike */ case CDP_SINGLETON + POLICY_MOBIKE_IX: + /* --session-resumption */ + case CDP_SINGLETON + POLICY_SESSION_RESUME_IX: + /* --ikefrag-allow */ case CDP_SINGLETON + POLICY_IKE_FRAG_ALLOW_IX: /* --ikefrag-force */ @@ -2474,7 +2489,8 @@ int main(int argc, char **argv) LELEM(OPT_DELETE) | LELEM(OPT_DELETEID) | LELEM(OPT_DELETEUSER) | LELEM(OPT_CD) | LELEM(OPT_REKEY_IKE) | - LELEM(OPT_REKEY_IPSEC))) { + LELEM(OPT_REKEY_IPSEC) | + LELEM(OPT_SESSION_SUSPEND))) { if (!LHAS(opts1_seen, OPT_NAME)) diag("missing --name "); } else if (msg.whack_options == LEMPTY) { @@ -2505,7 +2521,7 @@ int main(int argc, char **argv) msg.whack_addresspool_status || msg.whack_fips_status || msg.whack_brief_status || msg.whack_clear_stats || msg.whack_options || msg.whack_shutdown || msg.whack_purgeocsp || msg.whack_seccomp_crashtest || msg.whack_show_states || - msg.whack_rekey_ike || msg.whack_rekey_ipsec)) + msg.whack_rekey_ike || msg.whack_rekey_ipsec || msg.whack_suspend)) diag("no action specified; try --help for hints"); if (msg.policy & POLICY_AGGRESSIVE) {