Added IKEv1 key derivation with support for AUTH_CLASS_PSK.
authorTobias Brunner <tobias@strongswan.org>
Mon, 21 Nov 2011 10:41:37 +0000 (11:41 +0100)
committerTobias Brunner <tobias@strongswan.org>
Tue, 20 Mar 2012 16:30:45 +0000 (17:30 +0100)
src/libcharon/sa/keymat_v1.c
src/libcharon/sa/keymat_v1.h

index e4f58a4..59791e2 100644 (file)
@@ -44,35 +44,183 @@ struct private_keymat_v1_t {
         */
        pseudo_random_function_t prf_alg;
 
+       /**
+        * Key used for authentication during main mode
+        */
+       chunk_t skeyid;
+
+       /**
+        * Key to derive key material from for non-ISAKMP SAs, rekeying
+        */
+       chunk_t skeyid_d;
+
+       /**
+        * Key used for authentication after main mode
+        */
+       chunk_t skeyid_a;
 };
 
-METHOD(keymat_t, create_dh, diffie_hellman_t*,
-       private_keymat_v1_t *this, diffie_hellman_group_t group)
+/**
+ * Constants used in key derivation.
+ */
+static const chunk_t octet_0 = chunk_from_chars(0x00);
+static const chunk_t octet_1 = chunk_from_chars(0x01);
+static const chunk_t octet_2 = chunk_from_chars(0x02);
+
+
+/**
+ * Converts integrity algorithm to PRF algorithm
+ */
+static u_int16_t auth_to_prf(u_int16_t alg)
 {
-       return lib->crypto->create_dh(lib->crypto, group);;
+       switch (alg)
+       {
+               case AUTH_HMAC_SHA1_96:
+                       return PRF_HMAC_SHA1;
+               case AUTH_HMAC_SHA2_256_128:
+                       return PRF_HMAC_SHA2_256;
+               case AUTH_HMAC_SHA2_384_192:
+                       return PRF_HMAC_SHA2_384;
+               case AUTH_HMAC_SHA2_512_256:
+                       return PRF_HMAC_SHA2_512;
+               case AUTH_HMAC_MD5_96:
+                       return PRF_HMAC_MD5;
+               case AUTH_AES_XCBC_96:
+                       return PRF_AES128_XCBC;
+               default:
+                       return PRF_UNDEFINED;
+       }
 }
 
-METHOD(keymat_t, derive_ike_keys, bool,
-       private_keymat_v1_t *this, proposal_t *proposal, diffie_hellman_t *dh,
-       chunk_t nonce_i, chunk_t nonce_r, ike_sa_id_t *id,
-       pseudo_random_function_t rekey_function, chunk_t rekey_skd)
+/**
+ * Adjust the key length for PRF algorithms that expect a fixed key length.
+ */
+static void adjust_keylen(u_int16_t alg, chunk_t *key)
 {
-       return FALSE;
+       switch (alg)
+       {
+               case PRF_AES128_XCBC:
+                       /* while rfc4434 defines variable keys for AES-XCBC, rfc3664 does
+                                * not and therefore fixed key semantics apply to XCBC for key
+                                * derivation. */
+                       key->len = min(key->len, 16);
+                       break;
+               default:
+                       /* all other algorithms use variable key length */
+                       break;
+       }
 }
 
-METHOD(keymat_t, derive_child_keys, bool,
+METHOD(keymat_v1_t, derive_ike_keys, bool,
        private_keymat_v1_t *this, proposal_t *proposal, diffie_hellman_t *dh,
-       chunk_t nonce_i, chunk_t nonce_r, chunk_t *encr_i, chunk_t *integ_i,
-       chunk_t *encr_r, chunk_t *integ_r)
+       chunk_t dh_other, chunk_t nonce_i, chunk_t nonce_r, ike_sa_id_t *id,
+       auth_class_t auth, shared_key_t *shared_key)
 {
-       return FALSE;
+       chunk_t g_xy, g_xi, g_xr, dh_me, spi_i, spi_r, nonces, data, skeyid_e;
+       u_int16_t alg;
+
+       spi_i = chunk_alloca(sizeof(u_int64_t));
+       spi_r = chunk_alloca(sizeof(u_int64_t));
+
+       if (!proposal->get_algorithm(proposal, PSEUDO_RANDOM_FUNCTION, &alg, NULL))
+       {       /* no PRF negotiated, use HMAC version of integrity algorithm instead */
+               if (!proposal->get_algorithm(proposal, INTEGRITY_ALGORITHM, &alg, NULL)
+                       || (alg = auth_to_prf(alg)) == PRF_UNDEFINED)
+               {
+                       DBG1(DBG_IKE, "no %N selected",
+                                transform_type_names, PSEUDO_RANDOM_FUNCTION);
+                       return FALSE;
+               }
+       }
+       this->prf_alg = alg;
+       this->prf = lib->crypto->create_prf(lib->crypto, alg);
+       if (!this->prf)
+       {
+               DBG1(DBG_IKE, "%N %N not supported!",
+                        transform_type_names, PSEUDO_RANDOM_FUNCTION,
+                        pseudo_random_function_names, alg);
+               return FALSE;
+       }
+       if (this->prf->get_block_size(this->prf) <
+               this->prf->get_key_size(this->prf))
+       {       /* TODO-IKEv1: support PRF output expansion (RFC 2409, Appendix B) */
+               DBG1(DBG_IKE, "expansion of %N %N output not supported!",
+                        transform_type_names, PSEUDO_RANDOM_FUNCTION,
+                        pseudo_random_function_names, alg);
+               return FALSE;
+       }
+
+       if (dh->get_shared_secret(dh, &g_xy) != SUCCESS)
+       {
+               return FALSE;
+       }
+       DBG4(DBG_IKE, "shared Diffie Hellman secret %B", &g_xy);
+
+       *((u_int64_t*)spi_i.ptr) = id->get_initiator_spi(id);
+       *((u_int64_t*)spi_r.ptr) = id->get_responder_spi(id);
+       nonces = chunk_cata("cc", nonce_i, nonce_r);
+
+       switch (auth)
+       {
+               case AUTH_CLASS_PSK:
+               {       /* SKEYID = prf(pre-shared-key, Ni_b | Nr_b) */
+                       chunk_t psk;
+                       if (!shared_key)
+                       {
+                               chunk_clear(&g_xy);
+                               return FALSE;
+                       }
+                       psk = shared_key->get_key(shared_key);
+                       adjust_keylen(alg, &psk);
+                       this->prf->set_key(this->prf, psk);
+                       this->prf->allocate_bytes(this->prf, nonces, &this->skeyid);
+                       break;
+               }
+               case AUTH_CLASS_PUBKEY:
+               {
+                       /* signatures : SKEYID = prf(Ni_b | Nr_b, g^xy)
+                        * pubkey encr: SKEYID = prf(hash(Ni_b | Nr_b), CKY-I | CKY-R) */
+                       /* TODO-IKEv1: implement key derivation for other schemes,
+                        * fall for now */
+               }
+               default:
+                       /* authentication class not supported */
+                       chunk_clear(&g_xy);
+                       return FALSE;
+       }
+       adjust_keylen(alg, &this->skeyid);
+       DBG4(DBG_IKE, "SKEYID %B", &this->skeyid);
+
+       /* SKEYID_d = prf(SKEYID, g^xy | CKY-I | CKY-R | 0) */
+       data = chunk_cat("cccc", g_xy, spi_i, spi_r, octet_0);
+       this->prf->set_key(this->prf, this->skeyid);
+       this->prf->allocate_bytes(this->prf, data, &this->skeyid_d);
+       chunk_clear(&data);
+       DBG4(DBG_IKE, "SKEYID_d %B", &this->skeyid_d);
+
+       /* SKEYID_a = prf(SKEYID, SKEYID_d | g^xy | CKY-I | CKY-R | 1) */
+       data = chunk_cat("ccccc", this->skeyid_d, g_xy, spi_i, spi_r, octet_1);
+       this->prf->set_key(this->prf, this->skeyid);
+       this->prf->allocate_bytes(this->prf, data, &this->skeyid_a);
+       chunk_clear(&data);
+       DBG4(DBG_IKE, "SKEYID_a %B", &this->skeyid_a);
+
+       /* SKEYID_e = prf(SKEYID, SKEYID_a | g^xy | CKY-I | CKY-R | 2) */
+       data = chunk_cat("ccccc", this->skeyid_a, g_xy, spi_i, spi_r, octet_2);
+       this->prf->set_key(this->prf, this->skeyid);
+       this->prf->allocate_bytes(this->prf, data, &skeyid_e);
+       chunk_clear(&data);
+       DBG4(DBG_IKE, "SKEYID_e %B", &skeyid_e);
+
+       chunk_clear(&g_xy);
+
+       return TRUE;
 }
 
-METHOD(keymat_t, get_skd, pseudo_random_function_t,
-       private_keymat_v1_t *this, chunk_t *skd)
+METHOD(keymat_t, create_dh, diffie_hellman_t*,
+       private_keymat_v1_t *this, diffie_hellman_group_t group)
 {
-       *skd = chunk_empty;
-       return this->prf_alg;
+       return lib->crypto->create_dh(lib->crypto, group);
 }
 
 METHOD(keymat_t, get_aead, aead_t*,
@@ -85,6 +233,9 @@ METHOD(keymat_t, destroy, void,
        private_keymat_v1_t *this)
 {
        DESTROY_IF(this->prf);
+       chunk_clear(&this->skeyid);
+       chunk_clear(&this->skeyid_d);
+       chunk_clear(&this->skeyid_a);
        free(this);
 }
 
@@ -102,6 +253,7 @@ keymat_v1_t *keymat_v1_create(bool initiator)
                                .get_aead = _get_aead,
                                .destroy = _destroy,
                        },
+                       .derive_ike_keys = _derive_ike_keys,
                },
                .initiator = initiator,
                .prf_alg = PRF_UNDEFINED,
index 68057fa..e85d239 100644 (file)
@@ -34,6 +34,28 @@ struct keymat_v1_t {
         * Implements keymat_t.
         */
        keymat_t keymat;
+
+       /**
+        * Derive keys for the IKE_SA.
+        *
+        * These keys are not handed out, but are used by the associated signers,
+        * crypters and authentication functions.
+        *
+        * @param proposal              selected algorithms
+        * @param dh                    diffie hellman key allocated by create_dh()
+        * @param dh_other              public DH value from other peer
+        * @param nonce_i               initiators nonce value
+        * @param nonce_r               responders nonce value
+        * @param id                    IKE_SA identifier
+        * @param auth                  authentication method
+        * @param shared_key    PSK in case of AUTH_CLASS_PSK, NULL otherwise
+        * @return                              TRUE on success
+        */
+       bool (*derive_ike_keys)(keymat_v1_t *this, proposal_t *proposal,
+                                                       diffie_hellman_t *dh, chunk_t dh_other,
+                                                       chunk_t nonce_i, chunk_t nonce_r, ike_sa_id_t *id,
+                                                       auth_class_t auth, shared_key_t *shared_key);
+
 };
 
 /**