/* * Kernel runtime algorithm handling interface definitions * Originally by: JuanJo Ciarlante * Reworked into openswan 2.x by Michael Richardson * (C)opyright 2012 Paul Wouters * (C)opyright 2012-2013 Paul Wouters * (C)opyright 2012-2013 D. Hugh Redelmeier * * 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 #include #include #include #include #include "sysdep.h" #include "constants.h" #include "log.h" #include "lswalloc.h" #include "defs.h" #include "id.h" #include "connections.h" #include "state.h" #include "kernel_alg.h" #include "alg_info.h" #include "ike_alg.h" #include "plutoalg.h" #include "crypto.h" #include "spdb.h" #include "db_ops.h" #include "log.h" #include "whack.h" /** * Search oakley_enc_names for a match, eg: * "3des_cbc" <=> "OAKLEY_3DES_CBC" * * @param str String containing ALG name (eg: AES, 3DES) * @param len Length of str (note: not NUL-terminated) * @return int Registered # of ALG if loaded or -1 on failure. */ static int ealg_getbyname_ike(const char *const str, size_t len) { int ret; if (str == NULL || *str == '\0') return -1; ret = alg_enum_search(&oakley_enc_names, "OAKLEY_", "", str, len); if (ret >= 0) return ret; return alg_enum_search(&oakley_enc_names, "OAKLEY_", "_CBC", str, len); } /** * Search oakley_hash_names for a match, eg: * "md5" <=> "OAKLEY_MD5" * @param str String containing Hash name (eg: MD5, SHA1) * @param len Length of str (note: not NUL-terminated) * @return int Registered # of Hash ALG if loaded. */ static int aalg_getbyname_ike(const char *str, size_t len) { int ret = -1; int num_read; static const char sha2_256_aka[] = "sha2"; static const char sha1_aka[] = "sha"; DBG_log("entering aalg_getbyname_ike()"); if (str == NULL || str == '\0') return ret; /* handle "sha2" as "sha2_256" */ if (len == sizeof(sha2_256_aka)-1 && strncaseeq(str, sha2_256_aka, sizeof(sha2_256_aka)-1)) { DBG_log("interpreting sha2 as sha2_256"); str = "sha2_256"; len = strlen(str); } /* now "sha" as "sha1" */ if (len == sizeof(sha1_aka)-1 && strncaseeq(str, sha1_aka, sizeof(sha1_aka)-1)) { DBG_log("interpreting sha as sha1"); str = "sha1"; len = strlen(str); } ret = alg_enum_search(&oakley_hash_names, "OAKLEY_", "", str, len); if (ret >= 0) return ret; /* Special value for no authentication since zero is already used. */ ret = INT_MAX; if (len == 4 && strncaseeq(str, "null", len)) return ret; /* support idXXX as syntax, matching iana numbers directly */ /* ??? this sscanf is bogus since we don't know what appears at str[len] */ num_read = -1; if (sscanf(str, "id%d%n", &ret, &num_read) >= 1 && num_read == (int)len) return ret; return -1; } /** * Search oakley_group_names for a match, eg: * "modp1024" <=> "OAKLEY_GROUP_MODP1024" * @param str String MODP Name (eg: MODP) * @param len Length of str (note: not NUL-terminated) * @return int Registered # of MODP Group, if supported. */ static int modp_getbyname_ike(const char *const str, size_t len) { int ret = -1; if (str == NULL || *str == '\0') return -1; ret = alg_enum_search(&oakley_group_names, "OAKLEY_GROUP_", "", str, len); if (ret >= 0) return ret; return alg_enum_search(&oakley_group_names, "OAKLEY_GROUP_", " (extension)", str, len); } static void raw_alg_info_ike_add(struct alg_info_ike *alg_info, int ealg_id, unsigned ek_bits, int aalg_id, unsigned ak_bits, unsigned int modp_id) { struct ike_info *ike_info = alg_info->ike; unsigned cnt = alg_info->ai.alg_info_cnt, i; /* don't add duplicates */ for (i = 0; i < cnt; i++) { if (ike_info[i].ike_ealg == ealg_id && (!ek_bits || ike_info[i].ike_eklen == ek_bits) && ike_info[i].ike_halg == aalg_id && (!ak_bits || ike_info[i].ike_hklen == ak_bits) && ike_info[i].ike_modp == modp_id) { return; } } /* check for overflows */ passert(cnt < elemsof(alg_info->ike)); ike_info[cnt].ike_ealg = ealg_id; ike_info[cnt].ike_eklen = ek_bits; ike_info[cnt].ike_halg = aalg_id; ike_info[cnt].ike_hklen = ak_bits; ike_info[cnt].ike_modp = modp_id; alg_info->ai.alg_info_cnt++; DBG(DBG_CRYPT, DBG_log("raw_alg_info_ike_add() " "ealg=%d aalg=%d modp_id=%d, cnt=%d", ealg_id, aalg_id, modp_id, alg_info->ai.alg_info_cnt)); } /* * Proposals will be built by looping over default_ike_groups array and * merging alg_info (ike_info) contents */ static const int default_ike_groups[] = { DEFAULT_OAKLEY_GROUPS }; static const int default_ike_ealgs[] = { DEFAULT_OAKLEY_EALGS }; static const int default_ike_aalgs[] = { DEFAULT_OAKLEY_AALGS }; /* * Add IKE alg info _with_ logic (policy): */ static void per_group_alg_info_ike_add(struct alg_info *alg_info, int ealg_id, int ek_bits, int aalg_id, int ak_bits, int modp_id) { if (ealg_id == 0) { /* use all our default enc algs */ int i; for (i=0; i != elemsof(default_ike_ealgs); i++) { per_group_alg_info_ike_add(alg_info, default_ike_ealgs[i], ek_bits, aalg_id, ak_bits, modp_id); } return; } { if (aalg_id > 0) { raw_alg_info_ike_add( (struct alg_info_ike *)alg_info, ealg_id, ek_bits, aalg_id, ak_bits, modp_id); } else { int j; for (j=0; j != elemsof(default_ike_aalgs); j++) { raw_alg_info_ike_add( (struct alg_info_ike *)alg_info, ealg_id, ek_bits, default_ike_aalgs[j], ak_bits, modp_id); } } } } static void alg_info_ike_add(struct alg_info *alg_info, int ealg_id, int ek_bits, int aalg_id, int ak_bits, int modp_id) { if (modp_id == 0) { /* try each default group */ int i; for (i=0; i != elemsof(default_ike_groups); i++) per_group_alg_info_ike_add(alg_info, ealg_id, ek_bits, aalg_id, ak_bits, default_ike_groups[i]); } else { /* group determined by caller */ per_group_alg_info_ike_add(alg_info, ealg_id, ek_bits, aalg_id, ak_bits, modp_id); } } /* * print which ESP algorithm has actually been selected, based upon which * ones are actually loaded. */ static void alg_info_snprint_esp(char *buf, size_t buflen, struct alg_info_esp *alg_info) { char *ptr = buf; int ret; struct esp_info *esp_info; int cnt; int eklen, aklen; const char *sep = ""; passert(buflen >= sizeof("none")); ptr = buf; jam_str(buf, buflen, "none"); ALG_INFO_ESP_FOREACH(alg_info, esp_info, cnt) { if (kernel_alg_esp_enc_ok(esp_info->esp_ealg_id, 0) != NULL) { DBG_log("esp algid=%d not available", esp_info->esp_ealg_id); continue; } if (kernel_alg_esp_auth_ok(esp_info->esp_aalg_id, NULL) != NULL) { DBG_log("auth algid=%d not available", esp_info->esp_aalg_id); continue; } eklen = esp_info->esp_ealg_keylen; aklen = esp_info->esp_aalg_keylen; ret = snprintf(ptr, buflen, "%s%s(%d)_%03d-%s(%d)_%03d", sep, strip_prefix(enum_name(&esp_transformid_names, esp_info->esp_ealg_id), "ESP_"), esp_info->esp_ealg_id, eklen, strip_prefix( strip_prefix(enum_name(&auth_alg_names, esp_info->esp_aalg_id), "AUTH_ALGORITHM_HMAC_"), "AUTH_ALGORITHM_"), esp_info->esp_aalg_id, aklen); if (ret < 0 || (size_t)ret >= buflen) { DBG_log("alg_info_snprint_esp: buffer too short for snprintf"); break; } ptr += ret; buflen -= ret; sep = ", "; } } /* * print which AH algorithm has actually been selected, based upon which * ones are actually loaded. */ static void alg_info_snprint_ah(char *buf, size_t buflen, struct alg_info_esp *alg_info) { char *ptr = buf; int ret; struct esp_info *esp_info; int cnt; int aklen; const char *sep = ""; passert(buflen >= sizeof("none")); ptr = buf; jam_str(buf, buflen, "none"); ALG_INFO_ESP_FOREACH(alg_info, esp_info, cnt) { if (kernel_alg_esp_auth_ok(esp_info->esp_aalg_id, NULL) != NULL) { DBG_log("auth algid=%d not available", esp_info->esp_aalg_id); continue; } aklen = esp_info->esp_aalg_keylen; if (!aklen) aklen = kernel_alg_esp_auth_keylen( esp_info->esp_aalg_id) * BITS_PER_BYTE; ret = snprintf(ptr, buflen, "%s%s(%d)_%03d", sep, strip_prefix( strip_prefix(enum_name(&auth_alg_names, esp_info->esp_aalg_id), "AUTH_ALGORITHM_HMAC_"), "AUTH_ALGORITHM_"), esp_info->esp_aalg_id, aklen); if (ret < 0 || (size_t)ret >= buflen) { DBG_log("alg_info_snprint_ah: buffer too short for snprintf"); break; } ptr += ret; buflen -= ret; sep = ", "; } } void alg_info_snprint_phase2(char *buf, size_t buflen, struct alg_info_esp *alg_info) { switch (alg_info->ai.alg_info_protoid) { case PROTO_IPSEC_ESP: alg_info_snprint_esp(buf, buflen, alg_info); return; case PROTO_IPSEC_AH: alg_info_snprint_ah(buf, buflen, alg_info); return; default: bad_case(alg_info->ai.alg_info_protoid); } } void alg_info_snprint_ike(char *buf, size_t buflen, struct alg_info_ike *alg_info) { char *ptr = buf; int ret; struct ike_info *ike_info; int cnt; int eklen, aklen; const char *sep = ""; struct encrypt_desc *enc_desc; struct hash_desc *hash_desc; ALG_INFO_IKE_FOREACH(alg_info, ike_info, cnt) { if (ike_alg_enc_present(ike_info->ike_ealg) && (ike_alg_hash_present(ike_info->ike_halg)) && (lookup_group(ike_info->ike_modp) != NULL)) { enc_desc = ike_alg_get_encrypter(ike_info->ike_ealg); passert(enc_desc != NULL); hash_desc = ike_alg_get_hasher(ike_info->ike_halg); passert(hash_desc != NULL); eklen = ike_info->ike_eklen; if (!eklen) eklen = enc_desc->keydeflen; aklen = ike_info->ike_hklen; if (!aklen) aklen = hash_desc->hash_digest_len * BITS_PER_BYTE; ret = snprintf(ptr, buflen, "%s%s(%d)_%03d-%s(%d)_%03d-%s(%d)", sep, strip_prefix(enum_name(&oakley_enc_names, ike_info->ike_ealg), "OAKLEY_"), ike_info->ike_ealg, eklen, strip_prefix(enum_name(&oakley_hash_names, ike_info->ike_halg), "OAKLEY_"), ike_info->ike_halg, aklen, strip_prefix(enum_name(&oakley_group_names, ike_info->ike_modp), "OAKLEY_GROUP_"), ike_info->ike_modp); if (ret < 0 || (size_t)ret >= buflen) { DBG_log("alg_info_snprint_ike: buffer too short for snprintf"); break; } ptr += ret; buflen -= ret; sep = ", "; } } } /* * Must be called for each "new" char, with new * character in ctx.ch */ static void parser_init_ike(struct parser_context *p_ctx) { *p_ctx = empty_p_ctx; p_ctx->protoid = PROTO_ISAKMP; p_ctx->ealg_str = p_ctx->ealg_buf; p_ctx->aalg_str = p_ctx->aalg_buf; p_ctx->modp_str = p_ctx->modp_buf; p_ctx->state = ST_INI; p_ctx->ealg_getbyname = ealg_getbyname_ike; p_ctx->aalg_getbyname = aalg_getbyname_ike; p_ctx->modp_getbyname = modp_getbyname_ike; p_ctx->ealg_permit = TRUE; p_ctx->aalg_permit = TRUE; } struct alg_info_ike *alg_info_ike_create_from_str(const char *alg_str, char *err_buf, size_t err_buf_len) { /* * alg_info storage should be sized dynamically * but this may require two passes to know * transform count in advance. */ struct alg_info_ike *alg_info_ike = alloc_thing(struct alg_info_ike, "alg_info_ike"); return (struct alg_info_ike *) alg_info_parse_str( PROTO_ISAKMP, &alg_info_ike->ai, alg_str, err_buf, err_buf_len, parser_init_ike, alg_info_ike_add, lookup_group); } static bool kernel_alg_db_add(struct db_context *db_ctx, struct esp_info *esp_info, lset_t policy, bool logit) { int ealg_i = 0, aalg_i; if (policy & POLICY_ENCRYPT) { ealg_i = esp_info->esp_ealg_id; if (!ESP_EALG_PRESENT(ealg_i)) { if (logit) { loglog(RC_LOG_SERIOUS, "requested kernel enc ealg_id=%d not present", ealg_i); } else { DBG_log("requested kernel enc ealg_id=%d not present", ealg_i); } return FALSE; } } aalg_i = alg_info_esp_aa2sadb(esp_info->esp_aalg_id); if (!ESP_AALG_PRESENT(aalg_i)) { DBG_log("kernel_alg_db_add() kernel auth " "aalg_id=%d not present", aalg_i); return FALSE; } if (policy & POLICY_ENCRYPT) { /* open new transformation */ db_trans_add(db_ctx, ealg_i); /* add ESP auth attr (if present) */ if (esp_info->esp_aalg_id != AUTH_ALGORITHM_NONE) { db_attr_add_values(db_ctx, AUTH_ALGORITHM, esp_info->esp_aalg_id); } /* add keylegth if specified in esp= string */ if (esp_info->esp_ealg_keylen != 0) { db_attr_add_values(db_ctx, KEY_LENGTH, esp_info->esp_ealg_keylen); } else { /* no key length - if required add default here and add another max entry */ int def_ks = crypto_req_keysize(CRK_ESPorAH, ealg_i); if (def_ks) { int max_ks = BITS_PER_BYTE * kernel_alg_esp_enc_max_keylen(ealg_i); db_attr_add_values(db_ctx, KEY_LENGTH, def_ks); /* add this trans again with max key size */ if (def_ks != max_ks) { db_trans_add(db_ctx, ealg_i); if (esp_info->esp_aalg_id != AUTH_ALGORITHM_NONE) { db_attr_add_values(db_ctx, AUTH_ALGORITHM, esp_info->esp_aalg_id); } db_attr_add_values(db_ctx, KEY_LENGTH, max_ks); } } } } else if (policy & POLICY_AUTHENTICATE) { /* open new transformation */ db_trans_add(db_ctx, aalg_i); /* add ESP auth attr */ db_attr_add_values(db_ctx, AUTH_ALGORITHM, esp_info->esp_aalg_id); } return TRUE; } /* * Create proposal with runtime kernel algos, merging * with passed proposal if not NULL * * ??? is this still true? Certainly not free(3): * for now this function does free() previous returned * malloced pointer (this quirk allows easier spdb.c change) */ static struct db_context *kernel_alg_db_new(struct alg_info_esp *alg_info, lset_t policy, bool logit) { int ealg_i, aalg_i; unsigned int tn = 0; int i; const struct esp_info *esp_info; struct esp_info tmp_esp_info; struct db_context *ctx_new = NULL; struct db_trans *t; struct db_prop *prop; unsigned int trans_cnt = 0; bool success = TRUE; int protoid = 0; if (policy & POLICY_ENCRYPT) { trans_cnt = (esp_ealg_num * esp_aalg_num); protoid = PROTO_IPSEC_ESP; } else if (policy & POLICY_AUTHENTICATE) { trans_cnt = esp_aalg_num; protoid = PROTO_IPSEC_AH; } DBG(DBG_EMITTING, DBG_log("kernel_alg_db_new() " "initial trans_cnt=%d", trans_cnt)); /* pass aprox. number of transforms and attributes */ ctx_new = db_prop_new(protoid, trans_cnt, trans_cnt * 2); /* * Loop: for each element (struct esp_info) of * alg_info, if kernel support is present then * build the transform (and attrs) * * if NULL alg_info, propose everything ... */ if (alg_info != NULL) { ALG_INFO_ESP_FOREACH(alg_info, esp_info, i) { bool thistime; tmp_esp_info = *esp_info; thistime = kernel_alg_db_add(ctx_new, &tmp_esp_info, policy, logit); if (!thistime) success = FALSE; } } else { ESP_EALG_FOR_EACH_UPDOWN(ealg_i) { tmp_esp_info.esp_ealg_id = ealg_i; tmp_esp_info.esp_ealg_keylen = 0; ESP_AALG_FOR_EACH(aalg_i) { tmp_esp_info.esp_aalg_id = alg_info_esp_sadb2aa(aalg_i); tmp_esp_info.esp_aalg_keylen = 0; kernel_alg_db_add(ctx_new, &tmp_esp_info, policy, FALSE); } } } if (!success) { /* NO algorithms were found. oops */ db_destroy(ctx_new); return NULL; } prop = db_prop_get(ctx_new); DBG(DBG_CONTROL | DBG_EMITTING, DBG_log("kernel_alg_db_new() " "will return p_new->protoid=%d, p_new->trans_cnt=%d", prop->protoid, prop->trans_cnt)); for (t = prop->trans, tn = 0; t != NULL && t[tn].transid != 0 && tn < prop->trans_cnt; tn++) { DBG(DBG_CONTROL | DBG_EMITTING, DBG_log("kernel_alg_db_new() " " trans[%d]: transid=%d, attr_cnt=%d, " "attrs[0].type=%d, attrs[0].val=%d", tn, t[tn].transid, t[tn].attr_cnt, t[tn].attrs ? t[tn].attrs[0].type.ipsec : 255, t[tn].attrs ? t[tn].attrs[0].val : 255 )); } prop->trans_cnt = tn; return ctx_new; } bool ikev1_verify_phase2(int ealg, unsigned int key_len, int aalg, struct alg_info_esp *alg_info) { if (key_len == 0) key_len = crypto_req_keysize(CRK_ESPorAH, ealg); if (alg_info != NULL) { struct esp_info *esp_info; int i; ALG_INFO_ESP_FOREACH(alg_info, esp_info, i) { if (esp_info->esp_ealg_id == ealg && (esp_info->esp_ealg_keylen == 0 || key_len == 0 || esp_info->esp_ealg_keylen == key_len) && esp_info->esp_aalg_id == aalg) { return TRUE; } } libreswan_log( "IPsec Transform [%s (%d), %s] refused", enum_name(&esp_transformid_names, ealg), key_len, enum_name(&auth_alg_names, aalg)); return FALSE; } return TRUE; } void kernel_alg_show_status(void) { unsigned sadb_id, id; struct sadb_alg *alg_p; whack_log(RC_COMMENT, "ESP algorithms supported:"); whack_log(RC_COMMENT, " "); /* spacer */ ESP_EALG_FOR_EACH(sadb_id) { id = sadb_id; alg_p = &esp_ealg[sadb_id]; whack_log(RC_COMMENT, "algorithm ESP encrypt: id=%d, name=%s, " "ivlen=%d, keysizemin=%d, keysizemax=%d", id, enum_name(&esp_transformid_names, id), alg_p->sadb_alg_ivlen, alg_p->sadb_alg_minbits, alg_p->sadb_alg_maxbits); } ESP_AALG_FOR_EACH(sadb_id) { id = alg_info_esp_sadb2aa(sadb_id); alg_p = &esp_aalg[sadb_id]; whack_log(RC_COMMENT, "algorithm AH/ESP auth: id=%d, name=%s, " "keysizemin=%d, keysizemax=%d", id, enum_name(&auth_alg_names, id), alg_p->sadb_alg_minbits, alg_p->sadb_alg_maxbits); } whack_log(RC_COMMENT, " "); /* spacer */ } void kernel_alg_show_connection(struct connection *c, const char *instance) { struct state *st; const char *satype; const char *pfsbuf; if (c->policy & POLICY_ENCRYPT) satype = "ESP"; else if (c->policy & POLICY_AUTHENTICATE) satype = "AH"; else satype = "ESP+AH"; if (c->policy & POLICY_PFS) { if (c->alg_info_esp && c->alg_info_esp->esp_pfsgroup) { pfsbuf = strip_prefix(enum_show(&oakley_group_names, c->alg_info_esp->esp_pfsgroup), "OAKLEY_GROUP_"); } else { pfsbuf = ""; } } else { pfsbuf = ""; } if (c->alg_info_esp != NULL) { char buf[1024]; alg_info_snprint(buf, sizeof(buf), (struct alg_info *)c->alg_info_esp); whack_log(RC_COMMENT, "\"%s\"%s: %s algorithms wanted: %s", c->name, instance, satype, buf); alg_info_snprint_phase2(buf, sizeof(buf), c->alg_info_esp); whack_log(RC_COMMENT, "\"%s\"%s: %s algorithms loaded: %s", c->name, instance, satype, buf); } st = state_with_serialno(c->newest_ipsec_sa); if (st != NULL && st->st_esp.present) { whack_log(RC_COMMENT, "\"%s\"%s: %s algorithm newest: %s_%03d-%s; pfsgroup=%s", c->name, instance, satype, strip_prefix(enum_name(&esp_transformid_names, st->st_esp.attrs.transattrs.encrypt), "ESP_"), st->st_esp.attrs.transattrs.enckeylen, strip_prefix(enum_name(&auth_alg_names, st->st_esp.attrs.transattrs.integ_hash), "AUTH_ALGORITHM_"), pfsbuf); } if (st != NULL && st->st_ah.present) { whack_log(RC_COMMENT, "\"%s\"%s: %s algorithm newest: %s; pfsgroup=%s", c->name, instance, satype, strip_prefix(enum_name(&auth_alg_names, st->st_esp.attrs.transattrs.integ_hash), "AUTH_ALGORITHM_"), pfsbuf); } } struct db_sa *kernel_alg_makedb(lset_t policy, struct alg_info_esp *ei, bool logit) { struct db_context *dbnew; struct db_prop *p; struct db_prop_conj pc; struct db_sa t, *n; zero(&t); if (ei == NULL) { struct db_sa *sadb; lset_t pm = POLICY_ENCRYPT | POLICY_AUTHENTICATE; #if 0 if (can_do_IPcomp) pm |= POLICY_COMPRESS; #endif sadb = &ipsec_sadb[(policy & pm) >> POLICY_IPSEC_SHIFT]; /* make copy, to keep from freeing the static policies */ sadb = sa_copy_sa(sadb, 0); sadb->parentSA = FALSE; DBG(DBG_CONTROL, DBG_log("empty esp_info, returning defaults")); return sadb; } dbnew = kernel_alg_db_new(ei, policy, logit); if (!dbnew) { libreswan_log("failed to translate esp_info to proposal, returning empty"); return NULL; } p = db_prop_get(dbnew); if (!p) { libreswan_log("failed to get proposal from context, returning empty"); db_destroy(dbnew); return NULL; } pc.prop_cnt = 1; pc.props = p; t.prop_conj_cnt = 1; t.prop_conjs = &pc; /* make a fresh copy */ n = sa_copy_sa(&t, 0); n->parentSA = FALSE; db_destroy(dbnew); DBG(DBG_CONTROL, DBG_log("returning new proposal from esp_info")); return n; }