/* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include #include #include #include #include #include "config.h" #include "gcmalloc.h" #include "libpfkey.h" #include "var.h" #include "isakmp_var.h" #include "isakmp.h" #include "isakmp_xauth.h" #include "vmbuf.h" #include "crypto_openssl.h" #include "oakley.h" #include "ipsec_doi.h" #include "algorithm.h" #include "vendorid.h" #include "schedule.h" #include "pfkey.h" #include "nattraversal.h" #include "proposal.h" #include "sainfo.h" #include "localconf.h" #include "remoteconf.h" #include "sockmisc.h" #include "grabmyaddr.h" #include "plog.h" #include "admin.h" #include "privsep.h" #include "throttle.h" #include "misc.h" #include "handler.h" static struct localconf localconf; static struct sainfo sainfo; static char *pre_shared_key; static struct sockaddr *targets[2]; static struct sockaddr *source; static struct myaddrs myaddrs[2]; struct localconf *lcconf = &localconf; int f_local = 0; /*****************************************************************************/ static void add_sainfo_algorithm(int class, int algorithm, int length) { struct sainfoalg *p = calloc(1, sizeof(struct sainfoalg)); p->alg = algorithm; p->encklen = length; if (!sainfo.algs[class]) { sainfo.algs[class] = p; } else { struct sainfoalg *q = sainfo.algs[class]; while (q->next) { q = q->next; } q->next = p; } } static void add_sainfo() { if (pk_checkalg(algclass_ipsec_auth, algtype_hmac_sha2_512, 0) == 0) { add_sainfo_algorithm(algclass_ipsec_auth, IPSECDOI_ATTR_AUTH_HMAC_SHA2_512, 0); } else { do_plog(LLV_WARNING, "Kernel does not support SHA512, not enabling\n"); } if (pk_checkalg(algclass_ipsec_auth, algtype_hmac_sha2_384, 0) == 0) { add_sainfo_algorithm(algclass_ipsec_auth, IPSECDOI_ATTR_AUTH_HMAC_SHA2_384, 0); } else { do_plog(LLV_WARNING, "Kernel does not support SHA384, not enabling\n"); } add_sainfo_algorithm(algclass_ipsec_auth, IPSECDOI_ATTR_AUTH_HMAC_SHA1, 0); add_sainfo_algorithm(algclass_ipsec_auth, IPSECDOI_ATTR_AUTH_HMAC_SHA2_256, 0); add_sainfo_algorithm(algclass_ipsec_auth, IPSECDOI_ATTR_AUTH_HMAC_MD5, 0); add_sainfo_algorithm(algclass_ipsec_enc, IPSECDOI_ESP_AES, 256); add_sainfo_algorithm(algclass_ipsec_enc, IPSECDOI_ESP_AES, 128); add_sainfo_algorithm(algclass_ipsec_enc, IPSECDOI_ESP_3DES, 0); add_sainfo_algorithm(algclass_ipsec_enc, IPSECDOI_ESP_DES, 0); } static void set_globals(char *server) { struct addrinfo hints = { .ai_flags = AI_NUMERICSERV, #ifndef INET6 .ai_family = AF_INET, #else .ai_family = AF_UNSPEC, #endif .ai_socktype = SOCK_DGRAM, }; struct addrinfo *info; if (getaddrinfo(server, "500", &hints, &info) != 0) { do_plog(LLV_ERROR, "Cannot resolve address: %s\n", server); exit(1); } if (info->ai_next) { do_plog(LLV_WARNING, "Found multiple addresses. Use the first one.\n"); } targets[0] = dupsaddr(info->ai_addr); freeaddrinfo(info); source = getlocaladdr(targets[0]); if (!source) { do_plog(LLV_ERROR, "Cannot get local address\n"); exit(1); } set_port(targets[0], 0); set_port(source, 0); myaddrs[0].addr = dupsaddr(source); set_port(myaddrs[0].addr, PORT_ISAKMP); myaddrs[0].sock = -1; #ifdef ENABLE_NATT myaddrs[0].next = &myaddrs[1]; myaddrs[1].addr = dupsaddr(myaddrs[0].addr); set_port(myaddrs[1].addr, PORT_ISAKMP_NATT); myaddrs[1].sock = -1; myaddrs[1].udp_encap = 1; #endif localconf.myaddrs = &myaddrs[0]; localconf.port_isakmp = PORT_ISAKMP; localconf.port_isakmp_natt = PORT_ISAKMP_NATT; localconf.default_af = AF_INET; localconf.pathinfo[LC_PATHTYPE_CERT] = "./"; localconf.pad_random = LC_DEFAULT_PAD_RANDOM; localconf.pad_randomlen = LC_DEFAULT_PAD_RANDOM; localconf.pad_strict = LC_DEFAULT_PAD_STRICT; localconf.pad_excltail = LC_DEFAULT_PAD_EXCLTAIL; localconf.retry_counter = 10; localconf.retry_interval = 3; localconf.count_persend = LC_DEFAULT_COUNT_PERSEND; localconf.secret_size = LC_DEFAULT_SECRETSIZE; localconf.retry_checkph1 = LC_DEFAULT_RETRY_CHECKPH1; localconf.wait_ph2complete = LC_DEFAULT_WAIT_PH2COMPLETE; localconf.natt_ka_interval = LC_DEFAULT_NATT_KA_INTERVAL; sainfo.lifetime = IPSECDOI_ATTR_SA_LD_SEC_DEFAULT; sainfo.lifebyte = IPSECDOI_ATTR_SA_LD_KB_MAX; memset(script_names, 0, sizeof(script_names)); } /*****************************************************************************/ static int policy_match(struct sadb_address *address) { if (address) { struct sockaddr *addr = PFKEY_ADDR_SADDR(address); return !cmpsaddrwop(addr, targets[0]) || !cmpsaddrwop(addr, targets[1]); } return 0; } /* flush; spdflush; */ static void flush() { struct sadb_msg *p; int replies = 0; int key = pfkey_open(); if (pfkey_send_dump(key, SADB_SATYPE_UNSPEC) <= 0 || pfkey_send_spddump(key) <= 0) { do_plog(LLV_ERROR, "Cannot dump SAD and SPD\n"); exit(1); } for (p = NULL; replies < 2 && (p = pfkey_recv(key)) != NULL; free(p)) { caddr_t q[SADB_EXT_MAX + 1]; if (p->sadb_msg_type != SADB_DUMP && p->sadb_msg_type != SADB_X_SPDDUMP) { continue; } replies += !p->sadb_msg_seq; if (p->sadb_msg_errno || pfkey_align(p, q) || pfkey_check(q)) { continue; } if (policy_match((struct sadb_address *)q[SADB_EXT_ADDRESS_SRC]) || policy_match((struct sadb_address *)q[SADB_EXT_ADDRESS_DST])) { p->sadb_msg_type = (p->sadb_msg_type == SADB_DUMP) ? SADB_DELETE : SADB_X_SPDDELETE; p->sadb_msg_reserved = 0; p->sadb_msg_seq = 0; pfkey_send(key, p, PFKEY_UNUNIT64(p->sadb_msg_len)); } } pfkey_close(key); } /* spdadd src dst protocol -P out ipsec esp/transport//require; * spdadd dst src protocol -P in ipsec esp/transport//require; * or * spdadd src any protocol -P out ipsec esp/tunnel/local-remote/require; * spdadd any src protocol -P in ipsec esp/tunnel/remote-local/require; */ static void spdadd(struct sockaddr *src, struct sockaddr *dst, int protocol, struct sockaddr *local, struct sockaddr *remote) { struct __attribute__((packed)) { struct sadb_x_policy p; struct sadb_x_ipsecrequest q; char addresses[sizeof(struct sockaddr_storage) * 2]; } policy; struct sockaddr_storage any = { #ifndef __linux__ .ss_len = src->sa_len, #endif .ss_family = src->sa_family, }; int src_prefix = (src->sa_family == AF_INET) ? 32 : 128; int dst_prefix = src_prefix; int length = 0; int key; /* Fill values for outbound policy. */ memset(&policy, 0, sizeof(policy)); policy.p.sadb_x_policy_exttype = SADB_X_EXT_POLICY; policy.p.sadb_x_policy_type = IPSEC_POLICY_IPSEC; policy.p.sadb_x_policy_dir = IPSEC_DIR_OUTBOUND; #ifdef HAVE_PFKEY_POLICY_PRIORITY policy.p.sadb_x_policy_priority = PRIORITY_DEFAULT; #endif policy.q.sadb_x_ipsecrequest_proto = IPPROTO_ESP; policy.q.sadb_x_ipsecrequest_mode = IPSEC_MODE_TRANSPORT; policy.q.sadb_x_ipsecrequest_level = IPSEC_LEVEL_REQUIRE; /* Deal with tunnel mode. */ if (!dst) { int size = sysdep_sa_len(local); memcpy(policy.addresses, local, size); memcpy(&policy.addresses[size], remote, size); length += size + size; policy.q.sadb_x_ipsecrequest_mode = IPSEC_MODE_TUNNEL; dst = (struct sockaddr *)&any; dst_prefix = 0; /* Also use the source address to filter policies. */ targets[1] = dupsaddr(src); } /* Fix lengths. */ length += sizeof(policy.q); policy.q.sadb_x_ipsecrequest_len = length; length += sizeof(policy.p); policy.p.sadb_x_policy_len = PFKEY_UNIT64(length); /* Always do a flush before adding new policies. */ flush(); /* Set outbound policy. */ key = pfkey_open(); if (pfkey_send_spdadd(key, src, src_prefix, dst, dst_prefix, protocol, (caddr_t)&policy, length, 0) <= 0) { do_plog(LLV_ERROR, "Cannot set outbound policy\n"); exit(1); } /* Flip values for inbound policy. */ policy.p.sadb_x_policy_dir = IPSEC_DIR_INBOUND; if (!dst_prefix) { int size = sysdep_sa_len(local); memcpy(policy.addresses, remote, size); memcpy(&policy.addresses[size], local, size); } /* Set inbound policy. */ if (pfkey_send_spdadd(key, dst, dst_prefix, src, src_prefix, protocol, (caddr_t)&policy, length, 0) <= 0) { do_plog(LLV_ERROR, "Cannot set inbound policy\n"); exit(1); } pfkey_close(key); atexit(flush); } /*****************************************************************************/ static void add_proposal(struct remoteconf *remoteconf, int auth, int hash, int encryption, int length) { struct isakmpsa *p = racoon_calloc(1, sizeof(struct isakmpsa)); p->prop_no = 1; p->lifetime = OAKLEY_ATTR_SA_LD_SEC_DEFAULT; p->enctype = encryption; p->encklen = length; p->authmethod = auth; p->hashtype = hash; p->dh_group = OAKLEY_ATTR_GRP_DESC_MODP1024; p->vendorid = VENDORID_UNKNOWN; p->rmconf = remoteconf; if (!remoteconf->proposal) { p->trns_no = 1; remoteconf->proposal = p; } else { struct isakmpsa *q = remoteconf->proposal; while (q->next) { q = q->next; } p->trns_no = q->trns_no + 1; q->next = p; } } static vchar_t *strtovchar(char *string) { vchar_t *vchar = string ? vmalloc(strlen(string) + 1) : NULL; if (vchar) { memcpy(vchar->v, string, vchar->l); vchar->l -= 1; } return vchar; } static void set_pre_shared_key(struct remoteconf *remoteconf, char *identifier, char *key) { pre_shared_key = key; if (identifier[0]) { remoteconf->idv = strtovchar(identifier); remoteconf->etypes->type = ISAKMP_ETYPE_AGG; remoteconf->idvtype = IDTYPE_KEYID; if (strchr(identifier, '.')) { remoteconf->idvtype = IDTYPE_FQDN; if (strchr(identifier, '@')) { remoteconf->idvtype = IDTYPE_USERFQDN; } } } } static void set_certificates(struct remoteconf *remoteconf, char *user_private_key, char *user_certificate, char *ca_certificate, char *server_certificate) { remoteconf->myprivfile = user_private_key; remoteconf->mycertfile = user_certificate; if (user_certificate) { remoteconf->idvtype = IDTYPE_ASN1DN; } if (!ca_certificate[0]) { remoteconf->verify_cert = FALSE; } else { remoteconf->cacertfile = ca_certificate; } if (server_certificate[0]) { remoteconf->peerscertfile = server_certificate; remoteconf->getcert_method = ISAKMP_GETCERT_LOCALFILE; } } #ifdef ENABLE_HYBRID static void set_xauth_and_more(struct remoteconf *remoteconf, char *username, char *password, char *phase1_up, char *script_arg) { struct xauth_rmconf *xauth = racoon_calloc(1, sizeof(struct xauth_rmconf)); xauth->login = strtovchar(username); xauth->login->l += 1; xauth->pass = strtovchar(password); // Unlike the code that reads login, the code that reads pass does not // strip trailing nulls, so don't add one here. remoteconf->xauth = xauth; remoteconf->mode_cfg = TRUE; remoteconf->script[SCRIPT_PHASE1_UP] = strtovchar(phase1_up); script_names[SCRIPT_PHASE1_UP] = script_arg; } #endif extern void monitor_fd(int fd, void (*callback)(int)); void add_isakmp_handler(int fd, const char *interface) { if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, interface, strlen(interface))) { do_plog(LLV_WARNING, "Cannot bind socket to %s\n", interface); } monitor_fd(fd, (void *)isakmp_handler); } void setup(int argc, char **argv) { struct remoteconf *remoteconf = NULL; int auth; if (argc > 2) { set_globals(argv[2]); /* Initialize everything else. */ eay_init(); initrmconf(); oakley_dhinit(); compute_vendorids(); sched_init(); if (pfkey_init() < 0 || isakmp_init() < 0) { exit(1); } add_sainfo(); monitor_fd(localconf.sock_pfkey, (void *)pfkey_handler); add_isakmp_handler(myaddrs[0].sock, argv[1]); #ifdef ENABLE_NATT add_isakmp_handler(myaddrs[1].sock, argv[1]); natt_keepalive_init(); #endif /* Create remote configuration. */ remoteconf = newrmconf(); remoteconf->etypes = racoon_calloc(1, sizeof(struct etypes)); remoteconf->etypes->type = ISAKMP_ETYPE_IDENT; remoteconf->idvtype = IDTYPE_ADDRESS; remoteconf->ike_frag = TRUE; remoteconf->pcheck_level = PROP_CHECK_CLAIM; remoteconf->certtype = ISAKMP_CERT_X509SIGN; remoteconf->gen_policy = TRUE; remoteconf->nat_traversal = TRUE; remoteconf->dh_group = OAKLEY_ATTR_GRP_DESC_MODP1024; remoteconf->script[SCRIPT_PHASE1_UP] = strtovchar(""); remoteconf->script[SCRIPT_PHASE1_DOWN] = strtovchar(""); oakley_setdhgroup(remoteconf->dh_group, &remoteconf->dhgrp); remoteconf->remote = dupsaddr(targets[0]); } /* Set authentication method and credentials. */ if (argc == 7 && !strcmp(argv[3], "udppsk")) { set_pre_shared_key(remoteconf, argv[4], argv[5]); auth = OAKLEY_ATTR_AUTH_METHOD_PSKEY; set_port(targets[0], atoi(argv[6])); spdadd(source, targets[0], IPPROTO_UDP, NULL, NULL); } else if (argc == 9 && !strcmp(argv[3], "udprsa")) { set_certificates(remoteconf, argv[4], argv[5], argv[6], argv[7]); auth = OAKLEY_ATTR_AUTH_METHOD_RSASIG; set_port(targets[0], atoi(argv[8])); spdadd(source, targets[0], IPPROTO_UDP, NULL, NULL); #ifdef ENABLE_HYBRID } else if (argc == 10 && !strcmp(argv[3], "xauthpsk")) { set_pre_shared_key(remoteconf, argv[4], argv[5]); set_xauth_and_more(remoteconf, argv[6], argv[7], argv[8], argv[9]); auth = OAKLEY_ATTR_AUTH_METHOD_XAUTH_PSKEY_I; } else if (argc == 12 && !strcmp(argv[3], "xauthrsa")) { set_certificates(remoteconf, argv[4], argv[5], argv[6], argv[7]); set_xauth_and_more(remoteconf, argv[8], argv[9], argv[10], argv[11]); auth = OAKLEY_ATTR_AUTH_METHOD_XAUTH_RSASIG_I; } else if (argc == 10 && !strcmp(argv[3], "hybridrsa")) { set_certificates(remoteconf, NULL, NULL, argv[4], argv[5]); set_xauth_and_more(remoteconf, argv[6], argv[7], argv[8], argv[9]); auth = OAKLEY_ATTR_AUTH_METHOD_HYBRID_RSA_I; #endif } else { printf("Usage: %s [...], where [...] can be:\n" " udppsk ; \n" " udprsa \\\n" " ;\n" #ifdef ENABLE_HYBRID " xauthpsk \\\n" " ;\n" " xauthrsa \\\n" " \\\n" " ;\n" " hybridrsa \\\n" " ;\n" #endif "", argv[0]); exit(0); } /* Add proposals. */ add_proposal(remoteconf, auth, OAKLEY_ATTR_HASH_ALG_SHA2_384, OAKLEY_ATTR_ENC_ALG_AES, 256); add_proposal(remoteconf, auth, OAKLEY_ATTR_HASH_ALG_SHA2_256, OAKLEY_ATTR_ENC_ALG_AES, 256); // VPNs to openswan breaks when SHA2_512 is used as the first proposal. // openswan supports SHA2_256 or lower hash alg. With this add_proposal // order, openswan picks SHA2_256 and others pick SHA2_384 add_proposal(remoteconf, auth, OAKLEY_ATTR_HASH_ALG_SHA2_512, OAKLEY_ATTR_ENC_ALG_AES, 256); add_proposal(remoteconf, auth, OAKLEY_ATTR_HASH_ALG_SHA, OAKLEY_ATTR_ENC_ALG_AES, 256); add_proposal(remoteconf, auth, OAKLEY_ATTR_HASH_ALG_MD5, OAKLEY_ATTR_ENC_ALG_AES, 256); add_proposal(remoteconf, auth, OAKLEY_ATTR_HASH_ALG_SHA2_512, OAKLEY_ATTR_ENC_ALG_AES, 128); add_proposal(remoteconf, auth, OAKLEY_ATTR_HASH_ALG_SHA2_384, OAKLEY_ATTR_ENC_ALG_AES, 128); add_proposal(remoteconf, auth, OAKLEY_ATTR_HASH_ALG_SHA2_256, OAKLEY_ATTR_ENC_ALG_AES, 128); add_proposal(remoteconf, auth, OAKLEY_ATTR_HASH_ALG_SHA, OAKLEY_ATTR_ENC_ALG_AES, 128); add_proposal(remoteconf, auth, OAKLEY_ATTR_HASH_ALG_MD5, OAKLEY_ATTR_ENC_ALG_AES, 128); add_proposal(remoteconf, auth, OAKLEY_ATTR_HASH_ALG_SHA2_256, OAKLEY_ATTR_ENC_ALG_3DES, 0); add_proposal(remoteconf, auth, OAKLEY_ATTR_HASH_ALG_SHA, OAKLEY_ATTR_ENC_ALG_3DES, 0); add_proposal(remoteconf, auth, OAKLEY_ATTR_HASH_ALG_MD5, OAKLEY_ATTR_ENC_ALG_3DES, 0); add_proposal(remoteconf, auth, OAKLEY_ATTR_HASH_ALG_SHA2_256, OAKLEY_ATTR_ENC_ALG_DES, 0); add_proposal(remoteconf, auth, OAKLEY_ATTR_HASH_ALG_SHA, OAKLEY_ATTR_ENC_ALG_DES, 0); add_proposal(remoteconf, auth, OAKLEY_ATTR_HASH_ALG_MD5, OAKLEY_ATTR_ENC_ALG_DES, 0); /* Install remote configuration. */ insrmconf(remoteconf); /* Start phase 1 negotiation for xauth. */ if (remoteconf->xauth) { isakmp_ph1begin_i(remoteconf, remoteconf->remote, source); } } /*****************************************************************************/ /* localconf.h */ vchar_t *getpskbyaddr(struct sockaddr *addr) { return strtovchar(pre_shared_key); } vchar_t *getpskbyname(vchar_t *name) { return NULL; } void getpathname(char *path, int length, int type, const char *name) { if (pname) { snprintf(path, length, pname, name); } else { strncpy(path, name, length); } path[length - 1] = '\0'; } /* grabmyaddr.h */ int myaddr_getsport(struct sockaddr *addr) { return 0; } int getsockmyaddr(struct sockaddr *addr) { #ifdef ENABLE_NATT if (!cmpsaddrstrict(addr, myaddrs[1].addr)) { return myaddrs[1].sock; } #endif if (!cmpsaddrwop(addr, myaddrs[0].addr)) { return myaddrs[0].sock; } return -1; } /* privsep.h */ int privsep_pfkey_open() { return pfkey_open(); } void privsep_pfkey_close(int key) { pfkey_close(key); } vchar_t *privsep_eay_get_pkcs1privkey(char *file) { return eay_get_pkcs1privkey(file); } static char *get_env(char * const *envp, char *key) { int length = strlen(key); while (*envp && (strncmp(*envp, key, length) || (*envp)[length] != '=')) { ++envp; } return *envp ? &(*envp)[length + 1] : ""; } static int skip_script = 0; extern const char *android_hook(char **envp); int privsep_script_exec(char *script, int name, char * const *envp) { if (skip_script) { return 0; } skip_script = 1; if (name == SCRIPT_PHASE1_DOWN) { exit(1); } if (script_names[SCRIPT_PHASE1_UP]) { /* Racoon ignores INTERNAL_IP6_ADDRESS, so we only do IPv4. */ struct sockaddr *addr4 = str2saddr(get_env(envp, "INTERNAL_ADDR4"), NULL); struct sockaddr *local = str2saddr(get_env(envp, "LOCAL_ADDR"), get_env(envp, "LOCAL_PORT")); struct sockaddr *remote = str2saddr(get_env(envp, "REMOTE_ADDR"), get_env(envp, "REMOTE_PORT")); if (addr4 && local && remote) { #ifdef ANDROID_CHANGES if (pname) { script = (char *)android_hook((char **)envp); } #endif spdadd(addr4, NULL, IPPROTO_IP, local, remote); } else { do_plog(LLV_ERROR, "Cannot get parameters for SPD policy.\n"); exit(1); } racoon_free(addr4); racoon_free(local); racoon_free(remote); return script_exec(script, name, envp); } return 0; } int privsep_accounting_system(int port, struct sockaddr *addr, char *user, int status) { return 0; } int privsep_xauth_login_system(char *user, char *password) { return -1; } /* misc.h */ int racoon_hexdump(void *data, size_t length) { return 0; } /* sainfo.h */ struct sainfo *getsainfo(const vchar_t *src, const vchar_t *dst, const vchar_t *peer, int remoteid) { return &sainfo; } const char *sainfo2str(const struct sainfo *si) { return "*"; } /* throttle.h */ int throttle_host(struct sockaddr *addr, int fail) { return 0; } void shutdown_session() { flushph2(); flushph1(); isakmp_close(); pfkey_close(localconf.sock_pfkey); }