From ace3723d987987ef96c8905680a00497e7e1161c Mon Sep 17 00:00:00 2001 From: Jouni Malinen Date: Mon, 15 Jun 2020 20:20:50 +0300 Subject: [PATCH] DPP2: Enterprise provisioning (Enrollee) Add initial Enrollee functionality for provisioning enterprise (EAP-TLS) configuration object. This commit is handling only the most basic case and a number of TODO items remains to handle more complete CSR generation and config object processing. Signed-off-by: Jouni Malinen --- src/common/dpp.c | 121 ++++++++++++++++++++++++ src/common/dpp.h | 11 +++ src/common/dpp_crypto.c | 156 +++++++++++++++++++++++++++++++ src/common/wpa_ctrl.h | 3 + wpa_supplicant/dpp_supplicant.c | 161 +++++++++++++++++++++++++++++++- 5 files changed, 451 insertions(+), 1 deletion(-) diff --git a/src/common/dpp.c b/src/common/dpp.c index db8a68401..9a57a7095 100644 --- a/src/common/dpp.c +++ b/src/common/dpp.c @@ -829,6 +829,7 @@ struct wpabuf * dpp_build_conf_req_helper(struct dpp_authentication *auth, const char *tech = "infra"; const char *dpp_name; struct wpabuf *buf, *json; + char *csr = NULL; #ifdef CONFIG_TESTING_OPTIONS if (dpp_test == DPP_TEST_INVALID_CONFIG_ATTR_OBJ_CONF_REQ) { @@ -845,6 +846,17 @@ struct wpabuf * dpp_build_conf_req_helper(struct dpp_authentication *auth, len = 100 + name_len * 6 + 1 + int_array_len(opclasses) * 4; if (mud_url && mud_url[0]) len += 10 + os_strlen(mud_url); +#ifdef CONFIG_DPP2 + if (auth->csr) { + size_t csr_len; + + csr = base64_encode_no_lf(wpabuf_head(auth->csr), + wpabuf_len(auth->csr), &csr_len); + if (!csr) + return NULL; + len += 30 + csr_len; + } +#endif /* CONFIG_DPP2 */ json = wpabuf_alloc(len); if (!json) return NULL; @@ -871,10 +883,15 @@ struct wpabuf * dpp_build_conf_req_helper(struct dpp_authentication *auth, wpabuf_printf(json, "%s%u", i ? "," : "", opclasses[i]); json_end_array(json); } + if (csr) { + json_value_sep(json); + json_add_string(json, "pkcs10", csr); + } json_end_object(json); buf = dpp_build_conf_req(auth, wpabuf_head(json)); wpabuf_free(json); + os_free(csr); return buf; } @@ -1263,9 +1280,19 @@ void dpp_auth_deinit(struct dpp_authentication *auth) os_free(conf->connector); wpabuf_free(conf->c_sign_key); + wpabuf_free(conf->certbag); + wpabuf_free(conf->certs); + wpabuf_free(conf->cacert); + os_free(conf->server_name); } #ifdef CONFIG_DPP2 dpp_free_asymmetric_key(auth->conf_key_pkg); + os_free(auth->csrattrs); + wpabuf_free(auth->csr); + wpabuf_free(auth->priv_key); + wpabuf_free(auth->cacert); + wpabuf_free(auth->certbag); + os_free(auth->trusted_eap_server_name); #endif /* CONFIG_DPP2 */ wpabuf_free(auth->net_access_key); dpp_bootstrap_info_free(auth->tmp_own_bi); @@ -2459,6 +2486,58 @@ fail: } +#ifdef CONFIG_DPP2 +static int dpp_parse_cred_dot1x(struct dpp_authentication *auth, + struct dpp_config_obj *conf, + struct json_token *cred) +{ + struct json_token *ent, *name; + + ent = json_get_member(cred, "entCreds"); + if (!ent || ent->type != JSON_OBJECT) { + dpp_auth_fail(auth, "No entCreds in JSON"); + return -1; + } + + conf->certbag = json_get_member_base64(ent, "certBag"); + if (!conf->certbag) { + dpp_auth_fail(auth, "No certBag in JSON"); + return -1; + } + wpa_hexdump_buf(MSG_MSGDUMP, "DPP: Received certBag", conf->certbag); + conf->certs = dpp_pkcs7_certs(conf->certbag); + if (!conf->certs) { + dpp_auth_fail(auth, "No certificates in certBag"); + return -1; + } + + conf->cacert = json_get_member_base64(ent, "caCert"); + if (conf->cacert) + wpa_hexdump_buf(MSG_MSGDUMP, "DPP: Received caCert", + conf->cacert); + + name = json_get_member(ent, "trustedEapServerName"); + if (name && + (name->type != JSON_STRING || + has_ctrl_char((const u8 *) name->string, + os_strlen(name->string)))) { + dpp_auth_fail(auth, + "Invalid trustedEapServerName type in JSON"); + return -1; + } + if (name->string) { + wpa_printf(MSG_DEBUG, "DPP: Received trustedEapServerName: %s", + name->string); + conf->server_name = os_strdup(name->string); + if (!conf->server_name) + return -1; + } + + return 0; +} +#endif /* CONFIG_DPP2 */ + + const char * dpp_akm_str(enum dpp_akm akm) { switch (akm) { @@ -2678,6 +2757,12 @@ static int dpp_parse_conf_obj(struct dpp_authentication *auth, (auth->peer_version >= 2 && dpp_akm_legacy(conf->akm))) { if (dpp_parse_cred_dpp(auth, conf, cred) < 0) goto fail; +#ifdef CONFIG_DPP2 + } else if (conf->akm == DPP_AKM_DOT1X) { + if (dpp_parse_cred_dot1x(auth, conf, cred) < 0 || + dpp_parse_cred_dpp(auth, conf, cred) < 0) + goto fail; +#endif /* CONFIG_DPP2 */ } else { wpa_printf(MSG_DEBUG, "DPP: Unsupported akm: %s", token->string); @@ -2694,6 +2779,20 @@ fail: } +#ifdef CONFIG_DPP2 +static u8 * dpp_get_csr_attrs(const u8 *attrs, size_t attrs_len, size_t *len) +{ + const u8 *b64; + u16 b64_len; + + b64 = dpp_get_attr(attrs, attrs_len, DPP_ATTR_CSR_ATTR_REQ, &b64_len); + if (!b64) + return NULL; + return base64_decode((const char *) b64, b64_len, len); +} +#endif /* CONFIG_DPP2 */ + + int dpp_conf_resp_rx(struct dpp_authentication *auth, const struct wpabuf *resp) { @@ -2771,6 +2870,28 @@ int dpp_conf_resp_rx(struct dpp_authentication *auth, } auth->conf_resp_status = status[0]; wpa_printf(MSG_DEBUG, "DPP: Status %u", status[0]); +#ifdef CONFIG_DPP2 + if (status[0] == DPP_STATUS_CSR_NEEDED) { + u8 *csrattrs; + size_t csrattrs_len; + + wpa_printf(MSG_DEBUG, "DPP: Configurator requested CSR"); + + csrattrs = dpp_get_csr_attrs(unwrapped, unwrapped_len, + &csrattrs_len); + if (!csrattrs) { + dpp_auth_fail(auth, + "Missing or invalid CSR Attributes Request attribute"); + goto fail; + } + wpa_hexdump(MSG_DEBUG, "DPP: CsrAttrs", csrattrs, csrattrs_len); + os_free(auth->csrattrs); + auth->csrattrs = csrattrs; + auth->csrattrs_len = csrattrs_len; + ret = -2; + goto fail; + } +#endif /* CONFIG_DPP2 */ if (status[0] != DPP_STATUS_OK) { dpp_auth_fail(auth, "Configurator rejected configuration"); goto fail; diff --git a/src/common/dpp.h b/src/common/dpp.h index 8ca41abd2..a84cdc656 100644 --- a/src/common/dpp.h +++ b/src/common/dpp.h @@ -312,6 +312,10 @@ struct dpp_authentication { int psk_set; enum dpp_akm akm; struct wpabuf *c_sign_key; + struct wpabuf *certbag; + struct wpabuf *certs; + struct wpabuf *cacert; + char *server_name; } conf_obj[DPP_MAX_CONF_OBJ]; unsigned int num_conf_obj; struct dpp_asymmetric_key *conf_key_pkg; @@ -322,7 +326,11 @@ struct dpp_authentication { int akm_use_selector; int configurator_set; u8 transaction_id; + u8 *csrattrs; + size_t csrattrs_len; bool waiting_csr; + struct wpabuf *csr; + struct wpabuf *priv_key; /* DER-encoded private key used for csr */ bool waiting_cert; char *trusted_eap_server_name; struct wpabuf *cacert; @@ -606,6 +614,9 @@ struct dpp_pfs * dpp_pfs_init(const u8 *net_access_key, int dpp_pfs_process(struct dpp_pfs *pfs, const u8 *peer_ie, size_t peer_ie_len); void dpp_pfs_free(struct dpp_pfs *pfs); +struct wpabuf * dpp_build_csr(struct dpp_authentication *auth); +struct wpabuf * dpp_pkcs7_certs(const struct wpabuf *pkcs7); + struct dpp_bootstrap_info * dpp_add_qr_code(struct dpp_global *dpp, const char *uri); struct dpp_bootstrap_info * dpp_add_nfc_uri(struct dpp_global *dpp, diff --git a/src/common/dpp_crypto.c b/src/common/dpp_crypto.c index 8f884fd61..1990b82ff 100644 --- a/src/common/dpp_crypto.c +++ b/src/common/dpp_crypto.c @@ -12,6 +12,7 @@ #include #include #include +#include #include "utils/common.h" #include "utils/base64.h" @@ -2664,6 +2665,161 @@ void dpp_pfs_free(struct dpp_pfs *pfs) os_free(pfs); } + +struct wpabuf * dpp_build_csr(struct dpp_authentication *auth) +{ + X509_REQ *req = NULL; + struct wpabuf *buf = NULL; + unsigned char *der; + int der_len; + EVP_PKEY *key; + const EVP_MD *sign_md; + unsigned int hash_len = auth->curve->hash_len; + EC_KEY *eckey; + BIO *out = NULL; + + /* TODO: use auth->csrattrs */ + + /* TODO: support generation of a new private key if csrAttrs requests + * a specific group to be used */ + key = auth->own_protocol_key; + + eckey = EVP_PKEY_get1_EC_KEY(key); + if (!eckey) + goto fail; + der = NULL; + der_len = i2d_ECPrivateKey(eckey, &der); + if (der_len <= 0) + goto fail; + wpabuf_free(auth->priv_key); + auth->priv_key = wpabuf_alloc_copy(der, der_len); + OPENSSL_free(der); + if (!auth->priv_key) + goto fail; + + req = X509_REQ_new(); + if (!req || !X509_REQ_set_pubkey(req, key)) + goto fail; + + /* TODO */ + + /* TODO: hash func selection based on csrAttrs */ + if (hash_len == SHA256_MAC_LEN) { + sign_md = EVP_sha256(); + } else if (hash_len == SHA384_MAC_LEN) { + sign_md = EVP_sha384(); + } else if (hash_len == SHA512_MAC_LEN) { + sign_md = EVP_sha512(); + } else { + wpa_printf(MSG_DEBUG, "DPP: Unknown signature algorithm"); + goto fail; + } + + if (!X509_REQ_sign(req, key, sign_md)) + goto fail; + + der = NULL; + der_len = i2d_X509_REQ(req, &der); + if (der_len < 0) + goto fail; + buf = wpabuf_alloc_copy(der, der_len); + OPENSSL_free(der); + + wpa_hexdump_buf(MSG_DEBUG, "DPP: CSR", buf); + +fail: + BIO_free_all(out); + X509_REQ_free(req); + return buf; +} + + +struct wpabuf * dpp_pkcs7_certs(const struct wpabuf *pkcs7) +{ +#ifdef OPENSSL_IS_BORINGSSL + CBS pkcs7_cbs; +#else /* OPENSSL_IS_BORINGSSL */ + PKCS7 *p7 = NULL; + const unsigned char *p = wpabuf_head(pkcs7); +#endif /* OPENSSL_IS_BORINGSSL */ + STACK_OF(X509) *certs; + int i, num; + BIO *out = NULL; + size_t rlen; + struct wpabuf *pem = NULL; + int res; + +#ifdef OPENSSL_IS_BORINGSSL + certs = sk_X509_new_null(); + if (!certs) + goto fail; + CBS_init(&pkcs7_cbs, wpabuf_head(pkcs7), wpabuf_len(pkcs7)); + if (!PKCS7_get_certificates(certs, &pkcs7_cbs)) { + wpa_printf(MSG_INFO, "DPP: Could not parse PKCS#7 object: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto fail; + } +#else /* OPENSSL_IS_BORINGSSL */ + p7 = d2i_PKCS7(NULL, &p, wpabuf_len(pkcs7)); + if (!p7) { + wpa_printf(MSG_INFO, "DPP: Could not parse PKCS#7 object: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto fail; + } + + switch (OBJ_obj2nid(p7->type)) { + case NID_pkcs7_signed: + certs = p7->d.sign->cert; + break; + case NID_pkcs7_signedAndEnveloped: + certs = p7->d.signed_and_enveloped->cert; + break; + default: + certs = NULL; + break; + } +#endif /* OPENSSL_IS_BORINGSSL */ + + if (!certs || ((num = sk_X509_num(certs)) == 0)) { + wpa_printf(MSG_INFO, + "DPP: No certificates found in PKCS#7 object"); + goto fail; + } + + out = BIO_new(BIO_s_mem()); + if (!out) + goto fail; + + for (i = 0; i < num; i++) { + X509 *cert = sk_X509_value(certs, i); + + PEM_write_bio_X509(out, cert); + } + + rlen = BIO_ctrl_pending(out); + pem = wpabuf_alloc(rlen); + if (!pem) + goto fail; + res = BIO_read(out, wpabuf_put(pem, 0), rlen); + if (res <= 0) { + wpabuf_free(pem); + goto fail; + } + wpabuf_put(pem, res); + +fail: +#ifdef OPENSSL_IS_BORINGSSL + if (certs) + sk_X509_pop_free(certs, X509_free); +#else /* OPENSSL_IS_BORINGSSL */ + PKCS7_free(p7); +#endif /* OPENSSL_IS_BORINGSSL */ + if (out) + BIO_free_all(out); + + return pem; +} + #endif /* CONFIG_DPP2 */ diff --git a/src/common/wpa_ctrl.h b/src/common/wpa_ctrl.h index 1fa141520..58ce10141 100644 --- a/src/common/wpa_ctrl.h +++ b/src/common/wpa_ctrl.h @@ -179,6 +179,9 @@ extern "C" { #define DPP_EVENT_CONNECTOR "DPP-CONNECTOR " #define DPP_EVENT_C_SIGN_KEY "DPP-C-SIGN-KEY " #define DPP_EVENT_NET_ACCESS_KEY "DPP-NET-ACCESS-KEY " +#define DPP_EVENT_SERVER_NAME "DPP-SERVER-NAME " +#define DPP_EVENT_CERTBAG "DPP-CERTBAG " +#define DPP_EVENT_CACERT "DPP-CACERT " #define DPP_EVENT_MISSING_CONNECTOR "DPP-MISSING-CONNECTOR " #define DPP_EVENT_NETWORK_ID "DPP-NETWORK-ID " #define DPP_EVENT_CONFIGURATOR_ID "DPP-CONFIGURATOR-ID " diff --git a/wpa_supplicant/dpp_supplicant.c b/wpa_supplicant/dpp_supplicant.c index b0b657f5e..19d729f4d 100644 --- a/wpa_supplicant/dpp_supplicant.c +++ b/wpa_supplicant/dpp_supplicant.c @@ -49,6 +49,7 @@ wpas_dpp_tx_pkex_status(struct wpa_supplicant *wpa_s, #ifdef CONFIG_DPP2 static void wpas_dpp_reconfig_reply_wait_timeout(void *eloop_ctx, void *timeout_ctx); +static void wpas_dpp_start_gas_client(struct wpa_supplicant *wpa_s); #endif /* CONFIG_DPP2 */ static const u8 broadcast[ETH_ALEN] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; @@ -1218,6 +1219,106 @@ static struct wpa_ssid * wpas_dpp_add_network(struct wpa_supplicant *wpa_s, } } +#ifdef CONFIG_DPP2 + if (conf->akm == DPP_AKM_DOT1X) { + int i; + char name[100], blobname[128]; + struct wpa_config_blob *blob; + + ssid->key_mgmt = WPA_KEY_MGMT_IEEE8021X | + WPA_KEY_MGMT_IEEE8021X_SHA256 | + WPA_KEY_MGMT_IEEE8021X_SHA256; + ssid->ieee80211w = MGMT_FRAME_PROTECTION_OPTIONAL; + + if (conf->cacert) { + /* caCert is DER-encoded X.509v3 certificate for the + * server certificate if that is different from the + * trust root included in certBag. */ + /* TODO: ssid->eap.cert.ca_cert */ + } + + if (conf->certs) { + wpa_hexdump_buf(MSG_INFO, "JKM:certs", + conf->certs); + for (i = 0; ; i++) { + os_snprintf(name, sizeof(name), "dpp-certs-%d", + i); + if (!wpa_config_get_blob(wpa_s->conf, name)) + break; + } + + blob = os_zalloc(sizeof(*blob)); + if (!blob) + goto fail; + blob->len = wpabuf_len(conf->certs); + blob->name = os_strdup(name); + blob->data = os_malloc(blob->len); + if (!blob->name || !blob->data) { + wpa_config_free_blob(blob); + goto fail; + } + os_memcpy(blob->data, wpabuf_head(conf->certs), + blob->len); + os_snprintf(blobname, sizeof(blobname), "blob://%s", + name); + wpa_config_set_blob(wpa_s->conf, blob); + wpa_printf(MSG_DEBUG, "DPP: Added certificate blob %s", + name); + ssid->eap.cert.client_cert = os_strdup(blobname); + if (!ssid->eap.cert.client_cert) + goto fail; + + /* TODO: ssid->eap.identity from own certificate */ + if (wpa_config_set(ssid, "identity", "\"dpp-ent\"", + 0) < 0) + goto fail; + } + + if (auth->priv_key) { + for (i = 0; ; i++) { + os_snprintf(name, sizeof(name), "dpp-key-%d", + i); + if (!wpa_config_get_blob(wpa_s->conf, name)) + break; + } + + wpa_hexdump_buf(MSG_INFO, "JKM:privkey", + auth->priv_key); + blob = os_zalloc(sizeof(*blob)); + if (!blob) + goto fail; + blob->len = wpabuf_len(auth->priv_key); + blob->name = os_strdup(name); + blob->data = os_malloc(blob->len); + if (!blob->name || !blob->data) { + wpa_config_free_blob(blob); + goto fail; + } + os_memcpy(blob->data, wpabuf_head(auth->priv_key), + blob->len); + os_snprintf(blobname, sizeof(blobname), "blob://%s", + name); + wpa_config_set_blob(wpa_s->conf, blob); + wpa_printf(MSG_DEBUG, "DPP: Added private key blob %s", + name); + ssid->eap.cert.private_key = os_strdup(blobname); + if (!ssid->eap.cert.private_key) + goto fail; + } + + if (conf->server_name) { + ssid->eap.cert.domain_suffix_match = + os_strdup(conf->server_name); + if (!ssid->eap.cert.domain_suffix_match) + goto fail; + } + + /* TODO: Use entCreds::eapMethods */ + if (wpa_config_set(ssid, "eap", "TLS", 0) < 0) + goto fail; + } +#endif /* CONFIG_DPP2 */ + os_memcpy(wpa_s->dpp_last_ssid, conf->ssid, conf->ssid_len); wpa_s->dpp_last_ssid_len = conf->ssid_len; @@ -1346,6 +1447,32 @@ static int wpas_dpp_handle_config_obj(struct wpa_supplicant *wpa_s, } } +#ifdef CONFIG_DPP2 + if (conf->certbag) { + char *b64; + + b64 = base64_encode_no_lf(wpabuf_head(conf->certbag), + wpabuf_len(conf->certbag), NULL); + if (b64) + wpa_msg(wpa_s, MSG_INFO, DPP_EVENT_CERTBAG "%s", b64); + os_free(b64); + } + + if (conf->cacert) { + char *b64; + + b64 = base64_encode_no_lf(wpabuf_head(conf->cacert), + wpabuf_len(conf->cacert), NULL); + if (b64) + wpa_msg(wpa_s, MSG_INFO, DPP_EVENT_CACERT "%s", b64); + os_free(b64); + } + + if (conf->server_name) + wpa_msg(wpa_s, MSG_INFO, DPP_EVENT_SERVER_NAME "%s", + conf->server_name); +#endif /* CONFIG_DPP2 */ + return wpas_dpp_process_config(wpa_s, auth, conf); } @@ -1376,6 +1503,29 @@ static int wpas_dpp_handle_key_pkg(struct wpa_supplicant *wpa_s, } +#ifdef CONFIG_DPP2 +static void wpas_dpp_build_csr(void *eloop_ctx, void *timeout_ctx) +{ + struct wpa_supplicant *wpa_s = eloop_ctx; + struct dpp_authentication *auth = wpa_s->dpp_auth; + + if (!auth || !auth->csrattrs) + return; + + wpa_printf(MSG_DEBUG, "DPP: Build CSR"); + wpabuf_free(auth->csr); + /* TODO: Additional information needed for CSR based on csrAttrs */ + auth->csr = dpp_build_csr(auth); + if (!auth->csr) { + dpp_auth_deinit(wpa_s->dpp_auth); + wpa_s->dpp_auth = NULL; + } + + wpas_dpp_start_gas_client(wpa_s); +} +#endif /* CONFIG_DPP2 */ + + static void wpas_dpp_gas_resp_cb(void *ctx, const u8 *addr, u8 dialog_token, enum gas_query_result result, const struct wpabuf *adv_proto, @@ -1420,7 +1570,15 @@ static void wpas_dpp_gas_resp_cb(void *ctx, const u8 *addr, u8 dialog_token, goto fail; } - if (dpp_conf_resp_rx(auth, resp) < 0) { + res = dpp_conf_resp_rx(auth, resp); +#ifdef CONFIG_DPP2 + if (res == -2) { + wpa_printf(MSG_DEBUG, "DPP: CSR needed"); + eloop_register_timeout(0, 0, wpas_dpp_build_csr, wpa_s, NULL); + return; + } +#endif /* CONFIG_DPP2 */ + if (res < 0) { wpa_printf(MSG_DEBUG, "DPP: Configuration attempt failed"); goto fail; } @@ -3115,6 +3273,7 @@ void wpas_dpp_deinit(struct wpa_supplicant *wpa_s) eloop_cancel_timeout(wpas_dpp_conn_status_result_timeout, wpa_s, NULL); eloop_cancel_timeout(wpas_dpp_reconfig_reply_wait_timeout, wpa_s, NULL); + eloop_cancel_timeout(wpas_dpp_build_csr, wpa_s, NULL); dpp_pfs_free(wpa_s->dpp_pfs); wpa_s->dpp_pfs = NULL; wpas_dpp_chirp_stop(wpa_s);