/* mechanisms for preshared keys (public, private, and preshared secrets) * Copyright (C) 1998-2001 D. Hugh Redelmeier. * Copyright (C) 2009 Andreas Steffen - Hochschule fuer Technik Rapperswil * * 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 #include #include #include #include #include #include /* missing from on old systems */ #include #include #ifndef GLOB_ABORTED # define GLOB_ABORTED GLOB_ABEND /* fix for old versions */ #endif #include #include #include #include #include "constants.h" #include "defs.h" #include "x509.h" #include "certs.h" #include "smartcard.h" #include "connections.h" #include "state.h" #include "lex.h" #include "keys.h" #include "adns.h" /* needs */ #include "dnskey.h" /* needs keys.h and adns.h */ #include "log.h" #include "whack.h" /* for RC_LOG_SERIOUS */ #include "timer.h" #include "fetch.h" const char *shared_secrets_file = SHARED_SECRETS_FILE; typedef enum secret_kind_t secret_kind_t; enum secret_kind_t { SECRET_PSK, SECRET_PUBKEY, SECRET_XAUTH, SECRET_PIN }; typedef struct secret_t secret_t; struct secret_t { linked_list_t *ids; secret_kind_t kind; union { chunk_t preshared_secret; private_key_t *private_key; smartcard_t *smartcard; } u; secret_t *next; }; /* * free a public key struct */ static void free_public_key(pubkey_t *pk) { DESTROY_IF(pk->id); DESTROY_IF(pk->public_key); DESTROY_IF(pk->issuer); free(pk->serial.ptr); free(pk); } secret_t *secrets = NULL; /** * Find the secret associated with the combination of me and the peer. */ const secret_t* match_secret(identification_t *my_id, identification_t *his_id, secret_kind_t kind) { enum { /* bits */ match_default = 0x01, match_him = 0x02, match_me = 0x04 }; unsigned int best_match = 0; secret_t *s, *best = NULL; for (s = secrets; s != NULL; s = s->next) { unsigned int match = 0; if (s->kind != kind) { continue; } if (s->ids->get_count(s->ids) == 0) { /* a default (signified by lack of ids): * accept if no more specific match found */ match = match_default; } else { /* check if both ends match ids */ enumerator_t *enumerator; identification_t *id; enumerator = s->ids->create_enumerator(s->ids); while (enumerator->enumerate(enumerator, &id)) { if (my_id->equals(my_id, id)) { match |= match_me; } if (his_id->equals(his_id, id)) { match |= match_him; } } enumerator->destroy(enumerator); /* If our end matched the only id in the list, * default to matching any peer. * A more specific match will trump this. */ if (match == match_me && s->ids->get_count(s->ids) == 1) { match |= match_default; } } switch (match) { case match_me: /* if this is an asymmetric (eg. public key) system, * allow this-side-only match to count, even if * there are other ids in the list. */ if (kind != SECRET_PUBKEY) { break; } /* FALLTHROUGH */ case match_default: /* default all */ case match_me | match_default: /* default peer */ case match_me | match_him: /* explicit */ if (match == best_match) { /* two good matches are equally good: do they agree? */ bool same = FALSE; switch (kind) { case SECRET_PSK: case SECRET_XAUTH: same = chunk_equals(s->u.preshared_secret, best->u.preshared_secret); break; case SECRET_PUBKEY: same = s->u.private_key->equals(s->u.private_key, best->u.private_key); break; default: bad_case(kind); } if (!same) { loglog(RC_LOG_SERIOUS, "multiple ipsec.secrets entries with " "distinct secrets match endpoints: first secret used"); best = s; /* list is backwards: take latest in list */ } } else if (match > best_match) { /* this is the best match so far */ best_match = match; best = s; } } } return best; } /** * Retrieves an XAUTH secret primarily based on the user ID and * secondarily based on the server ID */ bool get_xauth_secret(identification_t *user, identification_t *server, chunk_t *secret) { const secret_t *s; s = match_secret(user, server, SECRET_XAUTH); if (s) { *secret = chunk_clone(s->u.preshared_secret); return TRUE; } else { *secret = chunk_empty; return FALSE; } } /** * We match the ID (if none, the IP address). Failure is indicated by a NULL. */ static const secret_t* get_secret(const connection_t *c, secret_kind_t kind) { identification_t *my_id, *his_id; const secret_t *best; my_id = c->spd.this.id; if (his_id_was_instantiated(c)) { /* roadwarrior: replace him with 0.0.0.0 */ his_id = identification_create_from_string("%any"); } else if (kind == SECRET_PSK && (c->policy & (POLICY_PSK | POLICY_XAUTH_PSK)) && ((c->kind == CK_TEMPLATE && c->spd.that.id->get_type(c->spd.that.id) == ID_ANY) || (c->kind == CK_INSTANCE && id_is_ipaddr(c->spd.that.id)))) { /* roadwarrior: replace him with 0.0.0.0 */ his_id = identification_create_from_string("%any"); } else { his_id = c->spd.that.id->clone(c->spd.that.id); } best = match_secret(my_id, his_id, kind); his_id->destroy(his_id); return best; } /* find the appropriate preshared key (see get_secret). * Failure is indicated by a NULL pointer. * Note: the result is not to be freed by the caller. */ const chunk_t* get_preshared_secret(const connection_t *c) { const secret_t *s = get_secret(c, SECRET_PSK); DBG(DBG_PRIVATE, if (s == NULL) DBG_log("no Preshared Key Found"); else DBG_dump_chunk("Preshared Key", s->u.preshared_secret); ) return s == NULL? NULL : &s->u.preshared_secret; } /* check the existence of a private key matching a public key contained * in an X.509 or OpenPGP certificate */ bool has_private_key(cert_t *cert) { secret_t *s; bool has_key = FALSE; public_key_t *pub_key = cert->cert->get_public_key(cert->cert); for (s = secrets; s != NULL; s = s->next) { if (s->kind == SECRET_PUBKEY && s->u.private_key->belongs_to(s->u.private_key, pub_key)) { has_key = TRUE; break; } } pub_key->destroy(pub_key); return has_key; } /* * get the matching private key belonging to a given X.509 certificate */ private_key_t* get_x509_private_key(const cert_t *cert) { public_key_t *public_key = cert->cert->get_public_key(cert->cert); private_key_t *private_key = NULL; secret_t *s; for (s = secrets; s != NULL; s = s->next) { if (s->kind == SECRET_PUBKEY && s->u.private_key->belongs_to(s->u.private_key, public_key)) { private_key = s->u.private_key; break; } } public_key->destroy(public_key); return private_key; } /* find the appropriate private key (see get_secret). * Failure is indicated by a NULL pointer. */ private_key_t* get_private_key(const connection_t *c) { const secret_t *s, *best = NULL; /* is a certificate assigned to this connection? */ if (c->spd.this.cert) { certificate_t *certificate; public_key_t *pub_key; certificate = c->spd.this.cert->cert; pub_key = certificate->get_public_key(certificate); for (s = secrets; s != NULL; s = s->next) { if (s->kind == SECRET_PUBKEY && s->u.private_key->belongs_to(s->u.private_key, pub_key)) { best = s; break; /* found the private key - no sense in searching further */ } } pub_key->destroy(pub_key); } else { best = get_secret(c, SECRET_PUBKEY); } return best ? best->u.private_key : NULL; } /* digest a secrets file * * The file is a sequence of records. A record is a maximal sequence of * tokens such that the first, and only the first, is in the first column * of a line. * * Tokens are generally separated by whitespace and are key words, ids, * strings, or data suitable for ttodata(3). As a nod to convention, * a trailing ":" on what would otherwise be a token is taken as a * separate token. If preceded by whitespace, a "#" is taken as starting * a comment: it and the rest of the line are ignored. * * One kind of record is an include directive. It starts with "include". * The filename is the only other token in the record. * If the filename does not start with /, it is taken to * be relative to the directory containing the current file. * * The other kind of record describes a key. It starts with a * sequence of ids and ends with key information. Each id * is an IP address, a Fully Qualified Domain Name (which will immediately * be resolved), or @FQDN which will be left as a name. * * The key part can be in several forms. * * The old form of the key is still supported: a simple * quoted strings (with no escapes) is taken as a preshred key. * * The new form starts the key part with a ":". * * For Preshared Key, use the "PSK" keyword, and follow it by a string * or a data token suitable for ttodata(3). * * For RSA Private Key, use the "RSA" keyword, followed by a * brace-enclosed list of key field keywords and data values. * The data values are large integers to be decoded by ttodata(3). * The fields are a subset of those used by BIND 8.2 and have the * same names. */ /* parse PSK from file */ static err_t process_psk_secret(chunk_t *psk) { err_t ugh = NULL; if (*tok == '"' || *tok == '\'') { chunk_t secret = { tok + 1, flp->cur - tok -2 }; *psk = chunk_clone(secret); (void) shift(); } else { char buf[BUF_LEN]; /* limit on size of binary representation of key */ size_t sz; ugh = ttodatav(tok, flp->cur - tok, 0, buf, sizeof(buf), &sz , diag_space, sizeof(diag_space), TTODATAV_SPACECOUNTS); if (ugh != NULL) { /* ttodata didn't like PSK data */ ugh = builddiag("PSK data malformed (%s): %s", ugh, tok); } else { chunk_t secret = { buf, sz }; *psk = chunk_clone(secret); (void) shift(); } } return ugh; } typedef enum rsa_private_key_part_t rsa_private_key_part_t; enum rsa_private_key_part_t { RSA_PART_MODULUS = 0, RSA_PART_PUBLIC_EXPONENT = 1, RSA_PART_PRIVATE_EXPONENT = 2, RSA_PART_PRIME1 = 3, RSA_PART_PRIME2 = 4, RSA_PART_EXPONENT1 = 5, RSA_PART_EXPONENT2 = 6, RSA_PART_COEFFICIENT = 7 }; const char *rsa_private_key_part_names[] = { "Modulus", "PublicExponent", "PrivateExponent", "Prime1", "Prime2", "Exponent1", "Exponent2", "Coefficient" }; /** * Parse fields of an RSA private key in BIND 8.2's representation * consistiong of a braced list of keyword and value pairs in required order. */ static err_t process_rsa_secret(private_key_t **key) { chunk_t rsa_chunk[countof(rsa_private_key_part_names)]; u_char buf[RSA_MAX_ENCODING_BYTES]; /* limit on size of binary representation of key */ rsa_private_key_part_t part, p; size_t sz; err_t ugh; for (part = RSA_PART_MODULUS; part <= RSA_PART_COEFFICIENT; part++) { const char *keyword = rsa_private_key_part_names[part]; if (!shift()) { ugh = "premature end of RSA key"; goto end; } if (!tokeqword(keyword)) { ugh = builddiag("%s keyword not found where expected in RSA key" , keyword); goto end; } if (!(shift() && (!tokeq(":") || shift()))) /* ignore optional ":" */ { ugh = "premature end of RSA key"; goto end; } ugh = ttodatav(tok, flp->cur - tok, 0, buf, sizeof(buf), &sz, diag_space, sizeof(diag_space), TTODATAV_SPACECOUNTS); if (ugh) { ugh = builddiag("RSA data malformed (%s): %s", ugh, tok); part++; goto end; } rsa_chunk[part] = chunk_create(buf, sz); rsa_chunk[part] = chunk_clone(rsa_chunk[part]); } /* We require an (indented) '}' and the end of the record. * We break down the test so that the diagnostic will be more helpful. * Some people don't seem to wish to indent the brace! */ if (!shift() || !tokeq("}")) { ugh = "malformed end of RSA private key -- indented '}' required"; goto end; } if (shift()) { ugh = "malformed end of RSA private key -- unexpected token after '}'"; goto end; } *key = lib->creds->create(lib->creds, CRED_PRIVATE_KEY, KEY_RSA, BUILD_RSA_MODULUS, rsa_chunk[RSA_PART_MODULUS], BUILD_RSA_PUB_EXP, rsa_chunk[RSA_PART_PUBLIC_EXPONENT], BUILD_RSA_PRIV_EXP, rsa_chunk[RSA_PART_PRIVATE_EXPONENT], BUILD_RSA_PRIME1, rsa_chunk[RSA_PART_PRIME1], BUILD_RSA_PRIME2, rsa_chunk[RSA_PART_PRIME2], BUILD_RSA_EXP1, rsa_chunk[RSA_PART_EXPONENT1], BUILD_RSA_EXP2, rsa_chunk[RSA_PART_EXPONENT2], BUILD_RSA_COEFF, rsa_chunk[RSA_PART_COEFFICIENT], BUILD_END); if (*key == NULL) { ugh = "parsing of RSA private key failed"; } end: /* clean up and return */ for (p = RSA_PART_MODULUS ; p < part; p++) { chunk_clear(&rsa_chunk[p]); } return ugh; } /** * process a key file protected with optional passphrase which can either be * read from ipsec.secrets or prompted for by using whack */ static err_t process_keyfile(private_key_t **key, key_type_t type, int whackfd) { char filename[BUF_LEN]; prompt_pass_t pass; memset(filename,'\0', BUF_LEN); memset(pass.secret,'\0', sizeof(pass.secret)); pass.prompt = FALSE; pass.fd = whackfd; /* we expect the filename of a PKCS#1 private key file */ if (*tok == '"' || *tok == '\'') /* quoted filename */ memcpy(filename, tok+1, flp->cur - tok - 2); else memcpy(filename, tok, flp->cur - tok); if (shift()) { /* we expect an appended passphrase or passphrase prompt*/ if (tokeqword("%prompt")) { if (pass.fd == NULL_FD) { return "Private key file -- enter passphrase using 'ipsec secrets'"; } pass.prompt = TRUE; } else { char *passphrase = tok; size_t len = flp->cur - passphrase; if (*tok == '"' || *tok == '\'') /* quoted passphrase */ { passphrase++; len -= 2; } if (len > PROMPT_PASS_LEN) { return "Private key file -- passphrase exceeds 64 characters"; } memcpy(pass.secret, passphrase, len); } if (shift()) { return "Private key file -- unexpected token after passphrase"; } } *key = load_private_key(filename, &pass, type); return *key ? NULL : "Private key file -- could not be loaded"; } /** * Process pin read from ipsec.secrets or prompted for it using whack */ static err_t process_pin(secret_t *s, int whackfd) { smartcard_t *sc; const char *pin_status = "no pin"; s->kind = SECRET_PIN; /* looking for the smartcard keyword */ if (!shift() || strncmp(tok, SCX_TOKEN, strlen(SCX_TOKEN)) != 0) return "PIN keyword must be followed by %smartcard:"; sc = scx_add(scx_parse_number_slot_id(tok + strlen(SCX_TOKEN))); s->u.smartcard = sc; scx_share(sc); if (sc->pin.ptr != NULL) { scx_release_context(sc); scx_free_pin(&sc->pin); } sc->valid = FALSE; if (!shift()) return "PIN statement must be terminated either by , %pinpad or %prompt"; if (tokeqword("%prompt")) { shift(); /* if whackfd exists, whack will be used to prompt for a pin */ if (whackfd != NULL_FD) pin_status = scx_get_pin(sc, whackfd) ? "valid pin" : "invalid pin"; else pin_status = "pin entry via prompt"; } else if (tokeqword("%pinpad")) { chunk_t empty_pin = { "", 0 }; shift(); /* pin will be entered via pin pad during verification */ sc->pin = chunk_clone(empty_pin); sc->pinpad = TRUE; sc->valid = TRUE; pin_status = "pin entry via pad"; if (pkcs11_keep_state) { scx_verify_pin(sc); } } else { /* we read the pin directly from ipsec.secrets */ err_t ugh = process_psk_secret(&sc->pin); if (ugh != NULL) return ugh; /* verify the pin */ pin_status = scx_verify_pin(sc) ? "valid PIN" : "invalid PIN"; } #ifdef SMARTCARD { char buf[BUF_LEN]; if (sc->any_slot) snprintf(buf, BUF_LEN, "any slot"); else snprintf(buf, BUF_LEN, "slot: %lu", sc->slot); plog(" %s for #%d (%s, id: %s)" , pin_status, sc->number, scx_print_slot(sc, ""), sc->id); } #else plog(" warning: SMARTCARD support is deactivated in pluto/Makefile!"); #endif return NULL; } static void log_psk(char *label, secret_t *s) { int n = 0; char buf[BUF_LEN]; enumerator_t *enumerator; identification_t *id; if (s->ids->get_count(s->ids) == 0) { n = snprintf(buf, BUF_LEN, "%%any"); } else { enumerator = s->ids->create_enumerator(s->ids); while(enumerator->enumerate(enumerator, &id)) { n += snprintf(buf + n, BUF_LEN - n, "%Y ", id); if (n >= BUF_LEN) { n = BUF_LEN - 1; break; } } enumerator->destroy(enumerator); } plog(" loaded %s secret for %.*s", label, n, buf); } static void process_secret(secret_t *s, int whackfd) { err_t ugh = NULL; s->kind = SECRET_PSK; /* default */ if (*tok == '"' || *tok == '\'') { log_psk("PSK", s); /* old PSK format: just a string */ ugh = process_psk_secret(&s->u.preshared_secret); } else if (tokeqword("psk")) { log_psk("PSK", s); /* preshared key: quoted string or ttodata format */ ugh = !shift()? "unexpected end of record in PSK" : process_psk_secret(&s->u.preshared_secret); } else if (tokeqword("xauth")) { s->kind = SECRET_XAUTH; log_psk("XAUTH", s); /* xauth secret: quoted string or ttodata format */ ugh = !shift()? "unexpected end of record in XAUTH" : process_psk_secret(&s->u.preshared_secret); } else if (tokeqword("rsa")) { /* RSA key: the fun begins. * A braced list of keyword and value pairs. */ s->kind = SECRET_PUBKEY; if (!shift()) { ugh = "bad RSA key syntax"; } else if (tokeq("{")) { ugh = process_rsa_secret(&s->u.private_key); } else { ugh = process_keyfile(&s->u.private_key, KEY_RSA, whackfd); } } else if (tokeqword("ecdsa")) { s->kind = SECRET_PUBKEY; if (!shift()) { ugh = "bad ECDSA key syntax"; } else { ugh = process_keyfile(&s->u.private_key, KEY_ECDSA, whackfd); } } else if (tokeqword("pin")) { ugh = process_pin(s, whackfd); } else { ugh = builddiag("unrecognized key format: %s", tok); } if (ugh != NULL) { loglog(RC_LOG_SERIOUS, "\"%s\" line %d: %s" , flp->filename, flp->lino, ugh); free(s); } else if (flushline("expected record boundary in key")) { /* gauntlet has been run: install new secret */ lock_certs_and_keys("process_secret"); s->next = secrets; secrets = s; unlock_certs_and_keys("process_secrets"); } } static void process_secrets_file(const char *file_pat, int whackfd); /* forward declaration */ static void process_secret_records(int whackfd) { /* read records from ipsec.secrets and load them into our table */ for (;;) { (void)flushline(NULL); /* silently ditch leftovers, if any */ if (flp->bdry == B_file) { break; } flp->bdry = B_none; /* eat the Record Boundary */ (void)shift(); /* get real first token */ if (tokeqword("include")) { /* an include directive */ char fn[MAX_TOK_LEN]; /* space for filename (I hope) */ char *p = fn; char *end_prefix = strrchr(flp->filename, '/'); if (!shift()) { loglog(RC_LOG_SERIOUS, "\"%s\" line %d: unexpected end of include directive" , flp->filename, flp->lino); continue; /* abandon this record */ } /* if path is relative and including file's pathname has * a non-empty dirname, prefix this path with that dirname. */ if (tok[0] != '/' && end_prefix != NULL) { size_t pl = end_prefix - flp->filename + 1; /* "clamp" length to prevent problems now; * will be rediscovered and reported later. */ if (pl > sizeof(fn)) { pl = sizeof(fn); } memcpy(fn, flp->filename, pl); p += pl; } if (flp->cur - tok >= &fn[sizeof(fn)] - p) { loglog(RC_LOG_SERIOUS, "\"%s\" line %d: include pathname too long" , flp->filename, flp->lino); continue; /* abandon this record */ } strcpy(p, tok); (void) shift(); /* move to Record Boundary, we hope */ if (flushline("ignoring malformed INCLUDE -- expected Record Boundary after filename")) { process_secrets_file(fn, whackfd); tok = NULL; /* correct, but probably redundant */ } } else { /* expecting a list of indices and then the key info */ secret_t *s = malloc_thing(secret_t); zero(s); s->ids = linked_list_create(); s->kind = SECRET_PSK; /* default */ s->u.preshared_secret = chunk_empty; s->next = NULL; for (;;) { if (tok[0] == '"' || tok[0] == '\'') { /* found key part */ process_secret(s, whackfd); break; } else if (tokeq(":")) { /* found key part */ shift(); /* discard explicit separator */ process_secret(s, whackfd); break; } else { identification_t *id; id = identification_create_from_string(tok); s->ids->insert_last(s->ids, id); if (!shift()) { /* unexpected Record Boundary or EOF */ loglog(RC_LOG_SERIOUS, "\"%s\" line %d: unexpected end of id list" , flp->filename, flp->lino); break; } } } } } } static int globugh(const char *epath, int eerrno) { log_errno_routine(eerrno, "problem with secrets file \"%s\"", epath); return 1; /* stop glob */ } static void process_secrets_file(const char *file_pat, int whackfd) { struct file_lex_position pos; char **fnp; glob_t globbuf; pos.depth = flp == NULL? 0 : flp->depth + 1; if (pos.depth > 10) { loglog(RC_LOG_SERIOUS, "preshared secrets file \"%s\" nested too deeply", file_pat); return; } /* do globbing */ { int r = glob(file_pat, GLOB_ERR, globugh, &globbuf); if (r != 0) { switch (r) { case GLOB_NOSPACE: loglog(RC_LOG_SERIOUS, "out of space processing secrets filename \"%s\"", file_pat); break; case GLOB_ABORTED: break; /* already logged */ case GLOB_NOMATCH: loglog(RC_LOG_SERIOUS, "no secrets filename matched \"%s\"", file_pat); break; default: loglog(RC_LOG_SERIOUS, "unknown glob error %d", r); break; } globfree(&globbuf); return; } } /* for each file... */ for (fnp = globbuf.gl_pathv; *fnp != NULL; fnp++) { if (lexopen(&pos, *fnp, FALSE)) { plog("loading secrets from \"%s\"", *fnp); (void) flushline("file starts with indentation (continuation notation)"); process_secret_records(whackfd); lexclose(); } } globfree(&globbuf); } void free_preshared_secrets(void) { lock_certs_and_keys("free_preshared_secrets"); if (secrets != NULL) { secret_t *s, *ns; plog("forgetting secrets"); for (s = secrets; s != NULL; s = ns) { ns = s->next; s->ids->destroy_offset(s->ids, offsetof(identification_t, destroy)); switch (s->kind) { case SECRET_PSK: case SECRET_XAUTH: free(s->u.preshared_secret.ptr); break; case SECRET_PUBKEY: DESTROY_IF(s->u.private_key); break; case SECRET_PIN: scx_release(s->u.smartcard); break; default: bad_case(s->kind); } free(s); } secrets = NULL; } unlock_certs_and_keys("free_preshard_secrets"); } void load_preshared_secrets(int whackfd) { free_preshared_secrets(); (void) process_secrets_file(shared_secrets_file, whackfd); } /* public key machinery * Note: caller must set dns_auth_level. */ pubkey_t* public_key_from_rsa(public_key_t *key) { pubkey_t *p = malloc_thing(pubkey_t); zero(p); p->id = identification_create_from_string("%any"); /* don't know, doesn't matter */ p->issuer = NULL; p->serial = chunk_empty; p->public_key = key; /* note that we return a 1 reference count upon creation: * invariant: recount > 0. */ p->refcnt = 1; return p; } /* Free a public key record. * As a convenience, this returns a pointer to next. */ pubkey_list_t* free_public_keyentry(pubkey_list_t *p) { pubkey_list_t *nxt = p->next; if (p->key != NULL) { unreference_key(&p->key); } free(p); return nxt; } void free_public_keys(pubkey_list_t **keys) { while (*keys != NULL) { *keys = free_public_keyentry(*keys); } } /* root of chained public key list */ pubkey_list_t *pubkeys = NULL; /* keys from ipsec.conf */ void free_remembered_public_keys(void) { free_public_keys(&pubkeys); } /** * Transfer public keys from *keys list to front of pubkeys list */ void transfer_to_public_keys(struct gw_info *gateways_from_dns #ifdef USE_KEYRR , pubkey_list_t **keys #endif /* USE_KEYRR */ ) { { struct gw_info *gwp; for (gwp = gateways_from_dns; gwp != NULL; gwp = gwp->next) { pubkey_list_t *pl = malloc_thing(pubkey_list_t); pl->key = gwp->key; /* note: this is a transfer */ gwp->key = NULL; /* really, it is! */ pl->next = pubkeys; pubkeys = pl; } } #ifdef USE_KEYRR { pubkey_list_t **pp = keys; while (*pp != NULL) { pp = &(*pp)->next; } *pp = pubkeys; pubkeys = *keys; *keys = NULL; } #endif /* USE_KEYRR */ } static void install_public_key(pubkey_t *pk, pubkey_list_t **head) { pubkey_list_t *p = malloc_thing(pubkey_list_t); /* install new key at front */ p->key = reference_key(pk); p->next = *head; *head = p; } void delete_public_keys(identification_t *id, key_type_t type, identification_t *issuer, chunk_t serial) { pubkey_list_t **pp, *p; pubkey_t *pk; key_type_t pk_type; for (pp = &pubkeys; (p = *pp) != NULL; ) { pk = p->key; pk_type = pk->public_key->get_type(pk->public_key); if (id->equals(id, pk->id) && pk_type == type && (issuer == NULL || pk->issuer == NULL || issuer->equals(issuer, pk->issuer)) && (serial.ptr == NULL || chunk_equals(serial, pk->serial))) { *pp = free_public_keyentry(p); } else { pp = &p->next; } } } pubkey_t* reference_key(pubkey_t *pk) { DBG(DBG_CONTROLMORE, DBG_log(" ref key: %p %p cnt %d '%Y'", pk, pk->public_key, pk->refcnt, pk->id) ) pk->refcnt++; return pk; } void unreference_key(pubkey_t **pkp) { pubkey_t *pk = *pkp; if (pk == NULL) { return; } DBG(DBG_CONTROLMORE, DBG_log("unref key: %p %p cnt %d '%Y'", pk, pk->public_key, pk->refcnt, pk->id) ) /* cancel out the pointer */ *pkp = NULL; passert(pk->refcnt != 0); pk->refcnt--; if (pk->refcnt == 0) { free_public_key(pk); } } bool add_public_key(identification_t *id, enum dns_auth_level dns_auth_level, enum pubkey_alg alg, chunk_t rfc3110_key, pubkey_list_t **head) { public_key_t *key = NULL; pubkey_t *pk; /* first: algorithm-specific decoding of key chunk */ switch (alg) { case PUBKEY_ALG_RSA: key = lib->creds->create(lib->creds, CRED_PUBLIC_KEY, KEY_RSA, BUILD_BLOB_DNSKEY, rfc3110_key, BUILD_END); if (key == NULL) { return FALSE; } break; default: bad_case(alg); } pk = malloc_thing(pubkey_t); zero(pk); pk->public_key = key; pk->id = id->clone(id); pk->dns_auth_level = dns_auth_level; pk->until_time = UNDEFINED_TIME; pk->issuer = NULL; pk->serial = chunk_empty; install_public_key(pk, head); return TRUE; } /** * Extract id and public key a certificate and insert it into a pubkeyrec */ void add_public_key_from_cert(cert_t *cert , time_t until, enum dns_auth_level dns_auth_level) { certificate_t *certificate = cert->cert; identification_t *subject = certificate->get_subject(certificate); identification_t *issuer = NULL; identification_t *id; chunk_t serialNumber = chunk_empty; pubkey_t *pk; key_type_t pk_type; /* ID type: ID_DER_ASN1_DN (X.509 subject field) */ pk = malloc_thing(pubkey_t); zero(pk); pk->public_key = certificate->get_public_key(certificate); pk_type = pk->public_key->get_type(pk->public_key); pk->id = subject->clone(subject); pk->dns_auth_level = dns_auth_level; pk->until_time = until; if (certificate->get_type(certificate) == CERT_X509) { x509_t *x509 = (x509_t*)certificate; issuer = certificate->get_issuer(certificate); serialNumber = x509->get_serial(x509); pk->issuer = issuer->clone(issuer); pk->serial = chunk_clone(serialNumber); } delete_public_keys(pk->id, pk_type, pk->issuer, pk->serial); install_public_key(pk, &pubkeys); if (certificate->get_type(certificate) == CERT_X509) { x509_t *x509 = (x509_t*)certificate; enumerator_t *enumerator; /* insert all subjectAltNames from X.509 certificates */ enumerator = x509->create_subjectAltName_enumerator(x509); while (enumerator->enumerate(enumerator, &id)) { if (id->get_type(id) != ID_ANY) { pk = malloc_thing(pubkey_t); zero(pk); pk->id = id->clone(id); pk->public_key = certificate->get_public_key(certificate); pk->dns_auth_level = dns_auth_level; pk->until_time = until; pk->issuer = issuer->clone(issuer); pk->serial = chunk_clone(serialNumber); delete_public_keys(pk->id, pk_type, pk->issuer, pk->serial); install_public_key(pk, &pubkeys); } } enumerator->destroy(enumerator); } else { pgp_certificate_t *pgp_cert = (pgp_certificate_t*)certificate; chunk_t fingerprint = pgp_cert->get_fingerprint(pgp_cert); /* add v3 or v4 PGP fingerprint */ pk = malloc_thing(pubkey_t); zero(pk); pk->id = identification_create_from_encoding(ID_KEY_ID, fingerprint); pk->public_key = certificate->get_public_key(certificate); pk->dns_auth_level = dns_auth_level; pk->until_time = until; delete_public_keys(pk->id, pk_type, pk->issuer, pk->serial); install_public_key(pk, &pubkeys); } } /* when a X.509 certificate gets revoked, all instances of * the corresponding public key must be removed */ void remove_x509_public_key(const cert_t *cert) { public_key_t *revoked_key = cert->cert->get_public_key(cert->cert); pubkey_list_t *p, **pp; p = pubkeys; pp = &pubkeys; while(p != NULL) { if (revoked_key->equals(revoked_key, p->key->public_key)) { /* remove p from list and free memory */ *pp = free_public_keyentry(p); loglog(RC_LOG_SERIOUS, "invalid public key deleted"); } else { pp = &p->next; } p =*pp; } revoked_key->destroy(revoked_key); } /* * list all public keys in the chained list */ void list_public_keys(bool utc) { pubkey_list_t *p = pubkeys; if (p != NULL) { whack_log(RC_COMMENT, " "); whack_log(RC_COMMENT, "List of Public Keys:"); } while (p != NULL) { pubkey_t *key = p->key; public_key_t *public = key->public_key; chunk_t keyid; whack_log(RC_COMMENT, " "); whack_log(RC_COMMENT, " identity: '%Y'", key->id); whack_log(RC_COMMENT, " pubkey: %N %4d bits, until %T %s", key_type_names, public->get_type(public), public->get_keysize(public) * BITS_PER_BYTE, &key->until_time, utc, check_expiry(key->until_time, PUBKEY_WARNING_INTERVAL, TRUE)); if (public->get_fingerprint(public, KEYID_PUBKEY_INFO_SHA1, &keyid)) { whack_log(RC_COMMENT," keyid: %#B", &keyid); } if (key->issuer) { whack_log(RC_COMMENT," issuer: \"%Y\"", key->issuer); } if (key->serial.len) { whack_log(RC_COMMENT," serial: %#B", &key->serial); } p = p->next; } }