HS 2.0R2: Add parse_cert command for debugging purposes
This hs20-osu-client client command can be used to parse a DER encoded X.509v3 certificate with the logotype extensions and id-wfa-hotspot-friendlyName values shown in detail. Signed-off-by: Jouni Malinen <jouni@qca.qualcomm.com>
This commit is contained in:
parent
c0d701a347
commit
39b420f7b1
3 changed files with 296 additions and 26 deletions
|
@ -2945,6 +2945,7 @@ static void usage(void)
|
||||||
"- dl_polupd_ca <PPS> <CA file>\n"
|
"- dl_polupd_ca <PPS> <CA file>\n"
|
||||||
"- dl_aaa_ca <PPS> <CA file>\n"
|
"- dl_aaa_ca <PPS> <CA file>\n"
|
||||||
"- browser <URL>\n"
|
"- browser <URL>\n"
|
||||||
|
"- parse_cert <X.509 certificate (DER)>\n"
|
||||||
"- osu_select <OSU info directory> [CA cert]\n");
|
"- osu_select <OSU info directory> [CA cert]\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3172,6 +3173,15 @@ int main(int argc, char *argv[])
|
||||||
argv[optind + 1]);
|
argv[optind + 1]);
|
||||||
ret = hs20_web_browser(argv[optind + 1]);
|
ret = hs20_web_browser(argv[optind + 1]);
|
||||||
wpa_printf(MSG_INFO, "Web browser result: %d", ret);
|
wpa_printf(MSG_INFO, "Web browser result: %d", ret);
|
||||||
|
} else if (strcmp(argv[optind], "parse_cert") == 0) {
|
||||||
|
if (argc - optind < 2) {
|
||||||
|
usage();
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
wpa_debug_level = MSG_MSGDUMP;
|
||||||
|
http_parse_x509_certificate(ctx.http, argv[optind + 1]);
|
||||||
|
wpa_debug_level = MSG_INFO;
|
||||||
} else {
|
} else {
|
||||||
wpa_printf(MSG_INFO, "Unknown command '%s'", argv[optind]);
|
wpa_printf(MSG_INFO, "Unknown command '%s'", argv[optind]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,5 +58,6 @@ void http_set_cert_cb(struct http_ctx *ctx,
|
||||||
int (*cb)(void *ctx, struct http_cert *cert),
|
int (*cb)(void *ctx, struct http_cert *cert),
|
||||||
void *cb_ctx);
|
void *cb_ctx);
|
||||||
const char * http_get_err(struct http_ctx *ctx);
|
const char * http_get_err(struct http_ctx *ctx);
|
||||||
|
void http_parse_x509_certificate(struct http_ctx *ctx, const char *fname);
|
||||||
|
|
||||||
#endif /* HTTP_UTILS_H */
|
#endif /* HTTP_UTILS_H */
|
||||||
|
|
|
@ -422,6 +422,8 @@ IMPLEMENT_ASN1_FUNCTIONS(LogotypeExtn);
|
||||||
#define sk_LogotypeInfo_value(st, i) SKM_sk_value(LogotypeInfo, (st), (i))
|
#define sk_LogotypeInfo_value(st, i) SKM_sk_value(LogotypeInfo, (st), (i))
|
||||||
#define sk_LogotypeImage_num(st) SKM_sk_num(LogotypeImage, (st))
|
#define sk_LogotypeImage_num(st) SKM_sk_num(LogotypeImage, (st))
|
||||||
#define sk_LogotypeImage_value(st, i) SKM_sk_value(LogotypeImage, (st), (i))
|
#define sk_LogotypeImage_value(st, i) SKM_sk_value(LogotypeImage, (st), (i))
|
||||||
|
#define sk_LogotypeAudio_num(st) SKM_sk_num(LogotypeAudio, (st))
|
||||||
|
#define sk_LogotypeAudio_value(st, i) SKM_sk_value(LogotypeAudio, (st), (i))
|
||||||
#define sk_HashAlgAndValue_num(st) SKM_sk_num(HashAlgAndValue, (st))
|
#define sk_HashAlgAndValue_num(st) SKM_sk_num(HashAlgAndValue, (st))
|
||||||
#define sk_HashAlgAndValue_value(st, i) SKM_sk_value(HashAlgAndValue, (st), (i))
|
#define sk_HashAlgAndValue_value(st, i) SKM_sk_value(HashAlgAndValue, (st), (i))
|
||||||
#define sk_ASN1_IA5STRING_num(st) SKM_sk_num(ASN1_IA5STRING, (st))
|
#define sk_ASN1_IA5STRING_num(st) SKM_sk_num(ASN1_IA5STRING, (st))
|
||||||
|
@ -556,6 +558,187 @@ static void add_logo_indirect(struct http_ctx *ctx, struct http_cert *hcert,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void i2r_HashAlgAndValue(HashAlgAndValue *hash, BIO *out, int indent)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
const unsigned char *data;
|
||||||
|
|
||||||
|
BIO_printf(out, "%*shashAlg: ", indent, "");
|
||||||
|
i2a_ASN1_OBJECT(out, hash->hashAlg->algorithm);
|
||||||
|
BIO_printf(out, "\n");
|
||||||
|
|
||||||
|
BIO_printf(out, "%*shashValue: ", indent, "");
|
||||||
|
data = hash->hashValue->data;
|
||||||
|
for (i = 0; i < hash->hashValue->length; i++)
|
||||||
|
BIO_printf(out, "%s%02x", i > 0 ? ":" : "", data[i]);
|
||||||
|
BIO_printf(out, "\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void i2r_LogotypeDetails(LogotypeDetails *details, BIO *out, int indent)
|
||||||
|
{
|
||||||
|
int i, num;
|
||||||
|
|
||||||
|
BIO_printf(out, "%*sLogotypeDetails\n", indent, "");
|
||||||
|
if (details->mediaType) {
|
||||||
|
BIO_printf(out, "%*smediaType: ", indent, "");
|
||||||
|
ASN1_STRING_print(out, details->mediaType);
|
||||||
|
BIO_printf(out, "\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
num = details->logotypeHash ?
|
||||||
|
sk_HashAlgAndValue_num(details->logotypeHash) : 0;
|
||||||
|
for (i = 0; i < num; i++) {
|
||||||
|
HashAlgAndValue *hash;
|
||||||
|
hash = sk_HashAlgAndValue_value(details->logotypeHash, i);
|
||||||
|
i2r_HashAlgAndValue(hash, out, indent);
|
||||||
|
}
|
||||||
|
|
||||||
|
num = details->logotypeURI ?
|
||||||
|
sk_ASN1_IA5STRING_num(details->logotypeURI) : 0;
|
||||||
|
for (i = 0; i < num; i++) {
|
||||||
|
ASN1_IA5STRING *uri;
|
||||||
|
uri = sk_ASN1_IA5STRING_value(details->logotypeURI, i);
|
||||||
|
BIO_printf(out, "%*slogotypeURI: ", indent, "");
|
||||||
|
ASN1_STRING_print(out, uri);
|
||||||
|
BIO_printf(out, "\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void i2r_LogotypeImageInfo(LogotypeImageInfo *info, BIO *out, int indent)
|
||||||
|
{
|
||||||
|
long val;
|
||||||
|
|
||||||
|
BIO_printf(out, "%*sLogotypeImageInfo\n", indent, "");
|
||||||
|
if (info->type) {
|
||||||
|
val = ASN1_INTEGER_get(info->type);
|
||||||
|
BIO_printf(out, "%*stype: %ld\n", indent, "", val);
|
||||||
|
} else {
|
||||||
|
BIO_printf(out, "%*stype: default (1)\n", indent, "");
|
||||||
|
}
|
||||||
|
val = ASN1_INTEGER_get(info->xSize);
|
||||||
|
BIO_printf(out, "%*sxSize: %ld\n", indent, "", val);
|
||||||
|
val = ASN1_INTEGER_get(info->ySize);
|
||||||
|
BIO_printf(out, "%*sySize: %ld\n", indent, "", val);
|
||||||
|
if (info->resolution) {
|
||||||
|
BIO_printf(out, "%*sresolution\n", indent, "");
|
||||||
|
/* TODO */
|
||||||
|
}
|
||||||
|
if (info->language) {
|
||||||
|
BIO_printf(out, "%*slanguage: ", indent, "");
|
||||||
|
ASN1_STRING_print(out, info->language);
|
||||||
|
BIO_printf(out, "\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void i2r_LogotypeImage(LogotypeImage *image, BIO *out, int indent)
|
||||||
|
{
|
||||||
|
BIO_printf(out, "%*sLogotypeImage\n", indent, "");
|
||||||
|
if (image->imageDetails) {
|
||||||
|
i2r_LogotypeDetails(image->imageDetails, out, indent + 4);
|
||||||
|
}
|
||||||
|
if (image->imageInfo) {
|
||||||
|
i2r_LogotypeImageInfo(image->imageInfo, out, indent + 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void i2r_LogotypeData(LogotypeData *data, const char *title, BIO *out,
|
||||||
|
int indent)
|
||||||
|
{
|
||||||
|
int i, num;
|
||||||
|
|
||||||
|
BIO_printf(out, "%*s%s - LogotypeData\n", indent, "", title);
|
||||||
|
|
||||||
|
num = data->image ? sk_LogotypeImage_num(data->image) : 0;
|
||||||
|
for (i = 0; i < num; i++) {
|
||||||
|
LogotypeImage *image = sk_LogotypeImage_value(data->image, i);
|
||||||
|
i2r_LogotypeImage(image, out, indent + 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
num = data->audio ? sk_LogotypeAudio_num(data->audio) : 0;
|
||||||
|
for (i = 0; i < num; i++) {
|
||||||
|
BIO_printf(out, "%*saudio: TODO\n", indent, "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void i2r_LogotypeReference(LogotypeReference *ref, const char *title,
|
||||||
|
BIO *out, int indent)
|
||||||
|
{
|
||||||
|
int i, hash_num, uri_num;
|
||||||
|
|
||||||
|
BIO_printf(out, "%*s%s - LogotypeReference\n", indent, "", title);
|
||||||
|
|
||||||
|
hash_num = ref->refStructHash ?
|
||||||
|
sk_HashAlgAndValue_num(ref->refStructHash) : 0;
|
||||||
|
uri_num = ref->refStructURI ?
|
||||||
|
sk_ASN1_IA5STRING_num(ref->refStructURI) : 0;
|
||||||
|
if (hash_num != uri_num) {
|
||||||
|
BIO_printf(out, "%*sUnexpected LogotypeReference array size difference %d != %d\n",
|
||||||
|
indent, "", hash_num, uri_num);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < hash_num; i++) {
|
||||||
|
HashAlgAndValue *hash;
|
||||||
|
ASN1_IA5STRING *uri;
|
||||||
|
|
||||||
|
hash = sk_HashAlgAndValue_value(ref->refStructHash, i);
|
||||||
|
i2r_HashAlgAndValue(hash, out, indent);
|
||||||
|
|
||||||
|
uri = sk_ASN1_IA5STRING_value(ref->refStructURI, i);
|
||||||
|
BIO_printf(out, "%*srefStructURI: ", indent, "");
|
||||||
|
ASN1_STRING_print(out, uri);
|
||||||
|
BIO_printf(out, "\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void i2r_LogotypeInfo(LogotypeInfo *info, const char *title, BIO *out,
|
||||||
|
int indent)
|
||||||
|
{
|
||||||
|
switch (info->type) {
|
||||||
|
case 0:
|
||||||
|
i2r_LogotypeData(info->d.direct, title, out, indent);
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
i2r_LogotypeReference(info->d.indirect, title, out, indent);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void debug_print_logotypeext(LogotypeExtn *logo)
|
||||||
|
{
|
||||||
|
BIO *out;
|
||||||
|
int i, num;
|
||||||
|
int indent = 0;
|
||||||
|
|
||||||
|
out = BIO_new_fp(stdout, BIO_NOCLOSE);
|
||||||
|
if (out == NULL)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (logo->communityLogos) {
|
||||||
|
num = sk_LogotypeInfo_num(logo->communityLogos);
|
||||||
|
for (i = 0; i < num; i++) {
|
||||||
|
LogotypeInfo *info;
|
||||||
|
info = sk_LogotypeInfo_value(logo->communityLogos, i);
|
||||||
|
i2r_LogotypeInfo(info, "communityLogo", out, indent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (logo->issuerLogo) {
|
||||||
|
i2r_LogotypeInfo(logo->issuerLogo, "issuerLogo", out, indent );
|
||||||
|
}
|
||||||
|
|
||||||
|
if (logo->subjectLogo) {
|
||||||
|
i2r_LogotypeInfo(logo->subjectLogo, "subjectLogo", out, indent);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (logo->otherLogos) {
|
||||||
|
BIO_printf(out, "%*sotherLogos - TODO\n", indent, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
BIO_free(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static void add_logotype_ext(struct http_ctx *ctx, struct http_cert *hcert,
|
static void add_logotype_ext(struct http_ctx *ctx, struct http_cert *hcert,
|
||||||
X509 *cert)
|
X509 *cert)
|
||||||
{
|
{
|
||||||
|
@ -600,6 +783,9 @@ static void add_logotype_ext(struct http_ctx *ctx, struct http_cert *hcert,
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (wpa_debug_level < MSG_INFO)
|
||||||
|
debug_print_logotypeext(logo);
|
||||||
|
|
||||||
if (!logo->communityLogos) {
|
if (!logo->communityLogos) {
|
||||||
wpa_printf(MSG_INFO, "No communityLogos included");
|
wpa_printf(MSG_INFO, "No communityLogos included");
|
||||||
LogotypeExtn_free(logo);
|
LogotypeExtn_free(logo);
|
||||||
|
@ -624,18 +810,51 @@ static void add_logotype_ext(struct http_ctx *ctx, struct http_cert *hcert,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void parse_cert(struct http_ctx *ctx, struct http_cert *hcert,
|
||||||
|
X509 *cert, GENERAL_NAMES **names)
|
||||||
|
{
|
||||||
|
os_memset(hcert, 0, sizeof(*hcert));
|
||||||
|
|
||||||
|
*names = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL);
|
||||||
|
if (*names)
|
||||||
|
add_alt_names(ctx, hcert, *names);
|
||||||
|
|
||||||
|
add_logotype_ext(ctx, hcert, cert);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void parse_cert_free(struct http_cert *hcert, GENERAL_NAMES *names)
|
||||||
|
{
|
||||||
|
unsigned int i;
|
||||||
|
|
||||||
|
for (i = 0; i < hcert->num_dnsname; i++)
|
||||||
|
OPENSSL_free(hcert->dnsname[i]);
|
||||||
|
os_free(hcert->dnsname);
|
||||||
|
|
||||||
|
for (i = 0; i < hcert->num_othername; i++)
|
||||||
|
os_free(hcert->othername[i].oid);
|
||||||
|
os_free(hcert->othername);
|
||||||
|
|
||||||
|
for (i = 0; i < hcert->num_logo; i++) {
|
||||||
|
os_free(hcert->logo[i].alg_oid);
|
||||||
|
os_free(hcert->logo[i].hash);
|
||||||
|
os_free(hcert->logo[i].uri);
|
||||||
|
}
|
||||||
|
os_free(hcert->logo);
|
||||||
|
|
||||||
|
sk_GENERAL_NAME_pop_free(names, GENERAL_NAME_free);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static int validate_server_cert(struct http_ctx *ctx, X509 *cert)
|
static int validate_server_cert(struct http_ctx *ctx, X509 *cert)
|
||||||
{
|
{
|
||||||
GENERAL_NAMES *names;
|
GENERAL_NAMES *names;
|
||||||
struct http_cert hcert;
|
struct http_cert hcert;
|
||||||
unsigned int i;
|
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
if (ctx->cert_cb == NULL)
|
if (ctx->cert_cb == NULL)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
os_memset(&hcert, 0, sizeof(hcert));
|
|
||||||
|
|
||||||
if (0) {
|
if (0) {
|
||||||
BIO *out;
|
BIO *out;
|
||||||
out = BIO_new_fp(stdout, BIO_NOCLOSE);
|
out = BIO_new_fp(stdout, BIO_NOCLOSE);
|
||||||
|
@ -643,35 +862,75 @@ static int validate_server_cert(struct http_ctx *ctx, X509 *cert)
|
||||||
BIO_free(out);
|
BIO_free(out);
|
||||||
}
|
}
|
||||||
|
|
||||||
names = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL);
|
parse_cert(ctx, &hcert, cert, &names);
|
||||||
if (names)
|
|
||||||
add_alt_names(ctx, &hcert, names);
|
|
||||||
|
|
||||||
add_logotype_ext(ctx, &hcert, cert);
|
|
||||||
|
|
||||||
ret = ctx->cert_cb(ctx->cert_cb_ctx, &hcert);
|
ret = ctx->cert_cb(ctx->cert_cb_ctx, &hcert);
|
||||||
|
parse_cert_free(&hcert, names);
|
||||||
for (i = 0; i < hcert.num_dnsname; i++)
|
|
||||||
OPENSSL_free(hcert.dnsname[i]);
|
|
||||||
os_free(hcert.dnsname);
|
|
||||||
|
|
||||||
for (i = 0; i < hcert.num_othername; i++)
|
|
||||||
os_free(hcert.othername[i].oid);
|
|
||||||
os_free(hcert.othername);
|
|
||||||
|
|
||||||
for (i = 0; i < hcert.num_logo; i++) {
|
|
||||||
os_free(hcert.logo[i].alg_oid);
|
|
||||||
os_free(hcert.logo[i].hash);
|
|
||||||
os_free(hcert.logo[i].uri);
|
|
||||||
}
|
|
||||||
os_free(hcert.logo);
|
|
||||||
|
|
||||||
sk_GENERAL_NAME_pop_free(names, GENERAL_NAME_free);
|
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void http_parse_x509_certificate(struct http_ctx *ctx, const char *fname)
|
||||||
|
{
|
||||||
|
BIO *in, *out;
|
||||||
|
X509 *cert;
|
||||||
|
GENERAL_NAMES *names;
|
||||||
|
struct http_cert hcert;
|
||||||
|
unsigned int i;
|
||||||
|
|
||||||
|
in = BIO_new_file(fname, "r");
|
||||||
|
if (in == NULL) {
|
||||||
|
wpa_printf(MSG_ERROR, "Could not read '%s'", fname);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
cert = d2i_X509_bio(in, NULL);
|
||||||
|
BIO_free(in);
|
||||||
|
|
||||||
|
if (cert == NULL) {
|
||||||
|
wpa_printf(MSG_ERROR, "Could not parse certificate");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
out = BIO_new_fp(stdout, BIO_NOCLOSE);
|
||||||
|
if (out) {
|
||||||
|
X509_print_ex(out, cert, XN_FLAG_COMPAT,
|
||||||
|
X509_FLAG_COMPAT);
|
||||||
|
BIO_free(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
wpa_printf(MSG_INFO, "Additional parsing information:");
|
||||||
|
parse_cert(ctx, &hcert, cert, &names);
|
||||||
|
for (i = 0; i < hcert.num_othername; i++) {
|
||||||
|
if (os_strcmp(hcert.othername[i].oid,
|
||||||
|
"1.3.6.1.4.1.40808.1.1.1") == 0) {
|
||||||
|
char *name = os_zalloc(hcert.othername[i].len + 1);
|
||||||
|
if (name) {
|
||||||
|
os_memcpy(name, hcert.othername[i].data,
|
||||||
|
hcert.othername[i].len);
|
||||||
|
wpa_printf(MSG_INFO,
|
||||||
|
"id-wfa-hotspot-friendlyName: %s",
|
||||||
|
name);
|
||||||
|
os_free(name);
|
||||||
|
}
|
||||||
|
wpa_hexdump_ascii(MSG_INFO,
|
||||||
|
"id-wfa-hotspot-friendlyName",
|
||||||
|
hcert.othername[i].data,
|
||||||
|
hcert.othername[i].len);
|
||||||
|
} else {
|
||||||
|
wpa_printf(MSG_INFO, "subjAltName[othername]: oid=%s",
|
||||||
|
hcert.othername[i].oid);
|
||||||
|
wpa_hexdump_ascii(MSG_INFO, "unknown othername",
|
||||||
|
hcert.othername[i].data,
|
||||||
|
hcert.othername[i].len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
parse_cert_free(&hcert, names);
|
||||||
|
|
||||||
|
X509_free(cert);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static int curl_cb_ssl_verify(int preverify_ok, X509_STORE_CTX *x509_ctx)
|
static int curl_cb_ssl_verify(int preverify_ok, X509_STORE_CTX *x509_ctx)
|
||||||
{
|
{
|
||||||
struct http_ctx *ctx;
|
struct http_ctx *ctx;
|
||||||
|
|
Loading…
Reference in a new issue