DPP: Bootstrap information management

Add wpa_supplicant control interface commands for parsing the bootstrap
info URI from a QR Code (get peer public key) and to generate a new
bootstrap info with private key for local use. The optional
key=<hexdump> argument to the DPP_BOOTSTRAP_GEN command can be used to
specify the bootstrapping private key in OpenSSL ECPrivateKey DER
encoding format. This results in the local bootstrapping information
entry being created with the specified key instead of generating a new
random one.

Signed-off-by: Jouni Malinen <jouni@qca.qualcomm.com>
This commit is contained in:
Jouni Malinen 2017-06-15 21:18:10 +03:00 committed by Jouni Malinen
parent d4d76d9835
commit be27e185b7
9 changed files with 1005 additions and 0 deletions

632
src/common/dpp.c Normal file
View file

@ -0,0 +1,632 @@
/*
* DPP functionality shared between hostapd and wpa_supplicant
* Copyright (c) 2017, Qualcomm Atheros, Inc.
*
* This software may be distributed under the terms of the BSD license.
* See README for more details.
*/
#include "utils/includes.h"
#include <openssl/err.h>
#include "utils/common.h"
#include "utils/base64.h"
#include "common/ieee802_11_common.h"
#include "crypto/crypto.h"
#include "dpp.h"
static const struct dpp_curve_params dpp_curves[] = {
/* The mandatory to support and the default NIST P-256 curve needs to
* be the first entry on this list. */
{ "prime256v1", 32, 32, 16, 32, "P-256" },
{ "secp384r1", 48, 48, 24, 48, "P-384" },
{ "secp521r1", 64, 64, 32, 66, "P-521" },
{ "brainpoolP256r1", 32, 32, 16, 32, "BP-256R1" },
{ "brainpoolP384r1", 48, 48, 24, 48, "BP-384R1" },
{ "brainpoolP512r1", 64, 64, 32, 64, "BP-512R1" },
{ NULL, 0, 0, 0, 0, NULL }
};
void dpp_bootstrap_info_free(struct dpp_bootstrap_info *info)
{
if (!info)
return;
os_free(info->uri);
os_free(info->info);
EVP_PKEY_free(info->pubkey);
os_free(info);
}
static int dpp_uri_valid_info(const char *info)
{
while (*info) {
unsigned char val = *info++;
if (val < 0x20 || val > 0x7e || val == 0x3b)
return 0;
}
return 1;
}
static int dpp_clone_uri(struct dpp_bootstrap_info *bi, const char *uri)
{
bi->uri = os_strdup(uri);
return bi->uri ? 0 : -1;
}
int dpp_parse_uri_chan_list(struct dpp_bootstrap_info *bi,
const char *chan_list)
{
const char *pos = chan_list;
int opclass, channel, freq;
while (pos && *pos && *pos != ';') {
opclass = atoi(pos);
if (opclass <= 0)
goto fail;
pos = os_strchr(pos, '/');
if (!pos)
goto fail;
pos++;
channel = atoi(pos);
if (channel <= 0)
goto fail;
while (*pos >= '0' && *pos <= '9')
pos++;
freq = ieee80211_chan_to_freq(NULL, opclass, channel);
wpa_printf(MSG_DEBUG,
"DPP: URI channel-list: opclass=%d channel=%d ==> freq=%d",
opclass, channel, freq);
if (freq < 0) {
wpa_printf(MSG_DEBUG,
"DPP: Ignore unknown URI channel-list channel (opclass=%d channel=%d)",
opclass, channel);
} else if (bi->num_freq == DPP_BOOTSTRAP_MAX_FREQ) {
wpa_printf(MSG_DEBUG,
"DPP: Too many channels in URI channel-list - ignore list");
bi->num_freq = 0;
break;
} else {
bi->freq[bi->num_freq++] = freq;
}
if (*pos == ';' || *pos == '\0')
break;
if (*pos != ',')
goto fail;
pos++;
}
return 0;
fail:
wpa_printf(MSG_DEBUG, "DPP: Invalid URI channel-list");
return -1;
}
int dpp_parse_uri_mac(struct dpp_bootstrap_info *bi, const char *mac)
{
if (!mac)
return 0;
if (hwaddr_aton2(mac, bi->mac_addr) < 0) {
wpa_printf(MSG_DEBUG, "DPP: Invalid URI mac");
return -1;
}
wpa_printf(MSG_DEBUG, "DPP: URI mac: " MACSTR, MAC2STR(bi->mac_addr));
return 0;
}
int dpp_parse_uri_info(struct dpp_bootstrap_info *bi, const char *info)
{
const char *end;
if (!info)
return 0;
end = os_strchr(info, ';');
if (!end)
end = info + os_strlen(info);
bi->info = os_malloc(end - info + 1);
if (!bi->info)
return -1;
os_memcpy(bi->info, info, end - info);
bi->info[end - info] = '\0';
wpa_printf(MSG_DEBUG, "DPP: URI(information): %s", bi->info);
if (!dpp_uri_valid_info(bi->info)) {
wpa_printf(MSG_DEBUG, "DPP: Invalid URI information payload");
return -1;
}
return 0;
}
static const struct dpp_curve_params *
dpp_get_curve_oid(const ASN1_OBJECT *poid)
{
ASN1_OBJECT *oid;
int i;
for (i = 0; dpp_curves[i].name; i++) {
oid = OBJ_txt2obj(dpp_curves[i].name, 0);
if (oid && OBJ_cmp(poid, oid) == 0)
return &dpp_curves[i];
}
return NULL;
}
static const struct dpp_curve_params * dpp_get_curve_nid(int nid)
{
int i, tmp;
if (!nid)
return NULL;
for (i = 0; dpp_curves[i].name; i++) {
tmp = OBJ_txt2nid(dpp_curves[i].name);
if (tmp == nid)
return &dpp_curves[i];
}
return NULL;
}
static int dpp_parse_uri_pk(struct dpp_bootstrap_info *bi, const char *info)
{
const char *end;
u8 *data;
size_t data_len;
EVP_PKEY *pkey;
const unsigned char *p;
int res;
X509_PUBKEY *pub = NULL;
ASN1_OBJECT *ppkalg;
const unsigned char *pk;
int ppklen;
X509_ALGOR *pa;
ASN1_OBJECT *pa_oid;
const void *pval;
int ptype;
const ASN1_OBJECT *poid;
char buf[100];
end = os_strchr(info, ';');
if (!end)
return -1;
data = base64_decode((const unsigned char *) info, end - info,
&data_len);
if (!data) {
wpa_printf(MSG_DEBUG,
"DPP: Invalid base64 encoding on URI public-key");
return -1;
}
wpa_hexdump(MSG_DEBUG, "DPP: Base64 decoded URI public-key",
data, data_len);
if (sha256_vector(1, (const u8 **) &data, &data_len,
bi->pubkey_hash) < 0) {
wpa_printf(MSG_DEBUG, "DPP: Failed to hash public key");
return -1;
}
/* DER encoded ASN.1 SubjectPublicKeyInfo
*
* SubjectPublicKeyInfo ::= SEQUENCE {
* algorithm AlgorithmIdentifier,
* subjectPublicKey BIT STRING }
*
* AlgorithmIdentifier ::= SEQUENCE {
* algorithm OBJECT IDENTIFIER,
* parameters ANY DEFINED BY algorithm OPTIONAL }
*
* subjectPublicKey = compressed format public key per ANSI X9.63
* algorithm = ecPublicKey (1.2.840.10045.2.1)
* parameters = shall be present and shall be OBJECT IDENTIFIER; e.g.,
* prime256v1 (1.2.840.10045.3.1.7)
*/
p = data;
pkey = d2i_PUBKEY(NULL, &p, data_len);
os_free(data);
if (!pkey) {
wpa_printf(MSG_DEBUG,
"DPP: Could not parse URI public-key SubjectPublicKeyInfo");
return -1;
}
if (EVP_PKEY_type(EVP_PKEY_id(pkey)) != EVP_PKEY_EC) {
wpa_printf(MSG_DEBUG,
"DPP: SubjectPublicKeyInfo does not describe an EC key");
EVP_PKEY_free(pkey);
return -1;
}
res = X509_PUBKEY_set(&pub, pkey);
if (res != 1) {
wpa_printf(MSG_DEBUG, "DPP: Could not set pubkey");
goto fail;
}
res = X509_PUBKEY_get0_param(&ppkalg, &pk, &ppklen, &pa, pub);
if (res != 1) {
wpa_printf(MSG_DEBUG,
"DPP: Could not extract SubjectPublicKeyInfo parameters");
goto fail;
}
res = OBJ_obj2txt(buf, sizeof(buf), ppkalg, 0);
if (res < 0 || (size_t) res >= sizeof(buf)) {
wpa_printf(MSG_DEBUG,
"DPP: Could not extract SubjectPublicKeyInfo algorithm");
goto fail;
}
wpa_printf(MSG_DEBUG, "DPP: URI subjectPublicKey algorithm: %s", buf);
if (os_strcmp(buf, "id-ecPublicKey") != 0) {
wpa_printf(MSG_DEBUG,
"DPP: Unsupported SubjectPublicKeyInfo algorithm");
goto fail;
}
X509_ALGOR_get0(&pa_oid, &ptype, (void *) &pval, pa);
if (ptype != V_ASN1_OBJECT) {
wpa_printf(MSG_DEBUG,
"DPP: SubjectPublicKeyInfo parameters did not contain an OID");
goto fail;
}
poid = pval;
res = OBJ_obj2txt(buf, sizeof(buf), poid, 0);
if (res < 0 || (size_t) res >= sizeof(buf)) {
wpa_printf(MSG_DEBUG,
"DPP: Could not extract SubjectPublicKeyInfo parameters OID");
goto fail;
}
wpa_printf(MSG_DEBUG, "DPP: URI subjectPublicKey parameters: %s", buf);
bi->curve = dpp_get_curve_oid(poid);
if (!bi->curve) {
wpa_printf(MSG_DEBUG,
"DPP: Unsupported SubjectPublicKeyInfo curve: %s",
buf);
goto fail;
}
wpa_hexdump(MSG_DEBUG, "DPP: URI subjectPublicKey", pk, ppklen);
X509_PUBKEY_free(pub);
bi->pubkey = pkey;
return 0;
fail:
X509_PUBKEY_free(pub);
EVP_PKEY_free(pkey);
return -1;
}
static struct dpp_bootstrap_info * dpp_parse_uri(const char *uri)
{
const char *pos = uri;
const char *end;
const char *chan_list = NULL, *mac = NULL, *info = NULL, *pk = NULL;
struct dpp_bootstrap_info *bi;
wpa_hexdump_ascii(MSG_DEBUG, "DPP: URI", uri, os_strlen(uri));
if (os_strncmp(pos, "DPP:", 4) != 0) {
wpa_printf(MSG_INFO, "DPP: Not a DPP URI");
return NULL;
}
pos += 4;
for (;;) {
end = os_strchr(pos, ';');
if (!end)
break;
if (end == pos) {
/* Handle terminating ";;" and ignore unexpected ";"
* for parsing robustness. */
pos++;
continue;
}
if (pos[0] == 'C' && pos[1] == ':' && !chan_list)
chan_list = pos + 2;
else if (pos[0] == 'M' && pos[1] == ':' && !mac)
mac = pos + 2;
else if (pos[0] == 'I' && pos[1] == ':' && !info)
info = pos + 2;
else if (pos[0] == 'K' && pos[1] == ':' && !pk)
pk = pos + 2;
else
wpa_hexdump_ascii(MSG_DEBUG,
"DPP: Ignore unrecognized URI parameter",
pos, end - pos);
pos = end + 1;
}
if (!pk) {
wpa_printf(MSG_INFO, "DPP: URI missing public-key");
return NULL;
}
bi = os_zalloc(sizeof(*bi));
if (!bi)
return NULL;
if (dpp_clone_uri(bi, uri) < 0 ||
dpp_parse_uri_chan_list(bi, chan_list) < 0 ||
dpp_parse_uri_mac(bi, mac) < 0 ||
dpp_parse_uri_info(bi, info) < 0 ||
dpp_parse_uri_pk(bi, pk) < 0) {
dpp_bootstrap_info_free(bi);
bi = NULL;
}
return bi;
}
struct dpp_bootstrap_info * dpp_parse_qr_code(const char *uri)
{
struct dpp_bootstrap_info *bi;
bi = dpp_parse_uri(uri);
if (bi)
bi->type = DPP_BOOTSTRAP_QR_CODE;
return bi;
}
static void dpp_debug_print_key(const char *title, EVP_PKEY *key)
{
EC_KEY *eckey;
BIO *out;
size_t rlen;
char *txt;
int res;
unsigned char *der = NULL;
int der_len;
out = BIO_new(BIO_s_mem());
if (!out)
return;
EVP_PKEY_print_private(out, key, 0, NULL);
rlen = BIO_ctrl_pending(out);
txt = os_malloc(rlen + 1);
if (txt) {
res = BIO_read(out, txt, rlen);
if (res > 0) {
txt[res] = '\0';
wpa_printf(MSG_DEBUG, "%s: %s", title, txt);
}
os_free(txt);
}
BIO_free(out);
eckey = EVP_PKEY_get1_EC_KEY(key);
if (!eckey)
return;
der_len = i2d_ECPrivateKey(eckey, &der);
if (der_len > 0)
wpa_hexdump_key(MSG_DEBUG, "DPP: ECPrivateKey", der, der_len);
OPENSSL_free(der);
if (der_len <= 0) {
der = NULL;
der_len = i2d_EC_PUBKEY(eckey, &der);
if (der_len > 0)
wpa_hexdump(MSG_DEBUG, "DPP: EC_PUBKEY", der, der_len);
OPENSSL_free(der);
}
EC_KEY_free(eckey);
}
static EVP_PKEY * dpp_gen_keypair(const struct dpp_curve_params *curve)
{
#ifdef OPENSSL_IS_BORINGSSL
EVP_PKEY_CTX *kctx = NULL;
const EC_GROUP *group;
EC_KEY *ec_params;
#else
EVP_PKEY_CTX *pctx, *kctx = NULL;
#endif
EVP_PKEY *params = NULL, *key = NULL;
int nid;
wpa_printf(MSG_DEBUG, "DPP: Generating a keypair");
nid = OBJ_txt2nid(curve->name);
if (nid == NID_undef) {
wpa_printf(MSG_INFO, "DPP: Unsupported curve %s", curve->name);
return NULL;
}
#ifdef OPENSSL_IS_BORINGSSL
group = EC_GROUP_new_by_curve_name(nid);
ec_params = EC_KEY_new();
if (!ec_params || EC_KEY_set_group(ec_params, group) != 1) {
wpa_printf(MSG_ERROR,
"DPP: Failed to generate EC_KEY parameters");
goto fail;
}
EC_KEY_set_asn1_flag(ec_params, OPENSSL_EC_NAMED_CURVE);
params = EVP_PKEY_new();
if (!params || EVP_PKEY_set1_EC_KEY(params, ec_params) != 1) {
wpa_printf(MSG_ERROR,
"DPP: Failed to generate EVP_PKEY parameters");
goto fail;
}
#else
pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL);
if (!pctx ||
EVP_PKEY_paramgen_init(pctx) != 1 ||
EVP_PKEY_CTX_set_ec_paramgen_curve_nid(pctx, nid) != 1 ||
EVP_PKEY_CTX_set_ec_param_enc(pctx, OPENSSL_EC_NAMED_CURVE) != 1 ||
EVP_PKEY_paramgen(pctx, &params) != 1) {
wpa_printf(MSG_ERROR,
"DPP: Failed to generate EVP_PKEY parameters");
EVP_PKEY_CTX_free(pctx);
goto fail;
}
EVP_PKEY_CTX_free(pctx);
#endif
kctx = EVP_PKEY_CTX_new(params, NULL);
if (!kctx ||
EVP_PKEY_keygen_init(kctx) != 1 ||
EVP_PKEY_keygen(kctx, &key) != 1) {
wpa_printf(MSG_ERROR, "DPP: Failed to generate EC key");
goto fail;
}
if (wpa_debug_show_keys)
dpp_debug_print_key("Own generated key", key);
EVP_PKEY_free(params);
EVP_PKEY_CTX_free(kctx);
return key;
fail:
EVP_PKEY_CTX_free(kctx);
EVP_PKEY_free(params);
return NULL;
}
static const struct dpp_curve_params *
dpp_get_curve_name(const char *name)
{
int i;
for (i = 0; dpp_curves[i].name; i++) {
if (os_strcmp(name, dpp_curves[i].name) == 0 ||
(dpp_curves[i].jwk_crv &&
os_strcmp(name, dpp_curves[i].jwk_crv) == 0))
return &dpp_curves[i];
}
return NULL;
}
static EVP_PKEY * dpp_set_keypair(const struct dpp_curve_params **curve,
const u8 *privkey, size_t privkey_len)
{
EVP_PKEY *pkey;
EC_KEY *eckey;
const EC_GROUP *group;
int nid;
pkey = EVP_PKEY_new();
if (!pkey)
return NULL;
eckey = d2i_ECPrivateKey(NULL, &privkey, privkey_len);
if (!eckey) {
wpa_printf(MSG_INFO,
"DPP: OpenSSL: d2i_ECPrivateKey() failed: %s",
ERR_error_string(ERR_get_error(), NULL));
EVP_PKEY_free(pkey);
return NULL;
}
group = EC_KEY_get0_group(eckey);
if (!group) {
EC_KEY_free(eckey);
EVP_PKEY_free(pkey);
return NULL;
}
nid = EC_GROUP_get_curve_name(group);
*curve = dpp_get_curve_nid(nid);
if (!*curve) {
wpa_printf(MSG_INFO,
"DPP: Unsupported curve (nid=%d) in pre-assigned key",
nid);
EC_KEY_free(eckey);
EVP_PKEY_free(pkey);
return NULL;
}
if (EVP_PKEY_assign_EC_KEY(pkey, eckey) != 1) {
EC_KEY_free(eckey);
EVP_PKEY_free(pkey);
return NULL;
}
return pkey;
}
char * dpp_keygen(struct dpp_bootstrap_info *bi, const char *curve,
const u8 *privkey, size_t privkey_len)
{
unsigned char *base64 = NULL;
char *pos, *end;
size_t len;
unsigned char *der = NULL;
int der_len;
EC_KEY *eckey;
if (!curve) {
bi->curve = &dpp_curves[0];
} else {
bi->curve = dpp_get_curve_name(curve);
if (!bi->curve) {
wpa_printf(MSG_INFO, "DPP: Unsupported curve: %s",
curve);
return NULL;
}
}
if (privkey)
bi->pubkey = dpp_set_keypair(&bi->curve, privkey, privkey_len);
else
bi->pubkey = dpp_gen_keypair(bi->curve);
if (!bi->pubkey)
goto fail;
bi->own = 1;
/* Need to get the compressed form of the public key through EC_KEY, so
* cannot use the simpler i2d_PUBKEY() here. */
eckey = EVP_PKEY_get1_EC_KEY(bi->pubkey);
if (!eckey)
goto fail;
EC_KEY_set_conv_form(eckey, POINT_CONVERSION_COMPRESSED);
der_len = i2d_EC_PUBKEY(eckey, &der);
EC_KEY_free(eckey);
if (der_len <= 0) {
wpa_printf(MSG_ERROR,
"DDP: Failed to build DER encoded public key");
goto fail;
}
len = der_len;
if (sha256_vector(1, (const u8 **) &der, &len, bi->pubkey_hash) < 0) {
wpa_printf(MSG_DEBUG, "DPP: Failed to hash public key");
goto fail;
}
base64 = base64_encode(der, der_len, &len);
OPENSSL_free(der);
if (!base64)
goto fail;
pos = (char *) base64;
end = pos + len;
for (;;) {
pos = os_strchr(pos, '\n');
if (!pos)
break;
os_memmove(pos, pos + 1, end - pos);
}
return (char *) base64;
fail:
os_free(base64);
OPENSSL_free(der);
return NULL;
}

56
src/common/dpp.h Normal file
View file

@ -0,0 +1,56 @@
/*
* DPP functionality shared between hostapd and wpa_supplicant
* Copyright (c) 2017, Qualcomm Atheros, Inc.
*
* This software may be distributed under the terms of the BSD license.
* See README for more details.
*/
#ifndef DPP_H
#define DPP_H
#include <openssl/x509.h>
#include "utils/list.h"
#include "crypto/sha256.h"
#define DPP_BOOTSTRAP_MAX_FREQ 30
struct dpp_curve_params {
const char *name;
size_t hash_len;
size_t aes_siv_key_len;
size_t nonce_len;
size_t prime_len;
const char *jwk_crv;
};
enum dpp_bootstrap_type {
DPP_BOOTSTRAP_QR_CODE,
};
struct dpp_bootstrap_info {
struct dl_list list;
unsigned int id;
enum dpp_bootstrap_type type;
char *uri;
u8 mac_addr[ETH_ALEN];
char *info;
unsigned int freq[DPP_BOOTSTRAP_MAX_FREQ];
unsigned int num_freq;
int own;
EVP_PKEY *pubkey;
u8 pubkey_hash[SHA256_MAC_LEN];
const struct dpp_curve_params *curve;
};
void dpp_bootstrap_info_free(struct dpp_bootstrap_info *info);
int dpp_parse_uri_chan_list(struct dpp_bootstrap_info *bi,
const char *chan_list);
int dpp_parse_uri_mac(struct dpp_bootstrap_info *bi, const char *mac);
int dpp_parse_uri_info(struct dpp_bootstrap_info *bi, const char *info);
struct dpp_bootstrap_info * dpp_parse_qr_code(const char *uri);
char * dpp_keygen(struct dpp_bootstrap_info *bi, const char *curve,
const u8 *privkey, size_t privkey_len);
#endif /* DPP_H */