From 39b420f7b12b152f879d1bb8a5267b9a838e04dc Mon Sep 17 00:00:00 2001 From: Jouni Malinen Date: Wed, 12 Mar 2014 00:29:36 +0200 Subject: [PATCH] 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 --- hs20/client/osu_client.c | 10 ++ src/utils/http-utils.h | 1 + src/utils/http_curl.c | 311 +++++++++++++++++++++++++++++++++++---- 3 files changed, 296 insertions(+), 26 deletions(-) diff --git a/hs20/client/osu_client.c b/hs20/client/osu_client.c index 1a4859235..2175fc353 100644 --- a/hs20/client/osu_client.c +++ b/hs20/client/osu_client.c @@ -2945,6 +2945,7 @@ static void usage(void) "- dl_polupd_ca \n" "- dl_aaa_ca \n" "- browser \n" + "- parse_cert \n" "- osu_select [CA cert]\n"); } @@ -3172,6 +3173,15 @@ int main(int argc, char *argv[]) argv[optind + 1]); ret = hs20_web_browser(argv[optind + 1]); 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 { wpa_printf(MSG_INFO, "Unknown command '%s'", argv[optind]); } diff --git a/src/utils/http-utils.h b/src/utils/http-utils.h index 4f5593d3f..8d4399a37 100644 --- a/src/utils/http-utils.h +++ b/src/utils/http-utils.h @@ -58,5 +58,6 @@ void http_set_cert_cb(struct http_ctx *ctx, int (*cb)(void *ctx, struct http_cert *cert), void *cb_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 */ diff --git a/src/utils/http_curl.c b/src/utils/http_curl.c index 34595655d..668c1a6a3 100644 --- a/src/utils/http_curl.c +++ b/src/utils/http_curl.c @@ -422,6 +422,8 @@ IMPLEMENT_ASN1_FUNCTIONS(LogotypeExtn); #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_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_value(st, i) SKM_sk_value(HashAlgAndValue, (st), (i)) #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, X509 *cert) { @@ -600,6 +783,9 @@ static void add_logotype_ext(struct http_ctx *ctx, struct http_cert *hcert, return; } + if (wpa_debug_level < MSG_INFO) + debug_print_logotypeext(logo); + if (!logo->communityLogos) { wpa_printf(MSG_INFO, "No communityLogos included"); 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) { GENERAL_NAMES *names; struct http_cert hcert; - unsigned int i; int ret; if (ctx->cert_cb == NULL) return 0; - os_memset(&hcert, 0, sizeof(hcert)); - if (0) { BIO *out; 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); } - 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); - + parse_cert(ctx, &hcert, cert, &names); ret = ctx->cert_cb(ctx->cert_cb_ctx, &hcert); - - 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); + parse_cert_free(&hcert, names); 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) { struct http_ctx *ctx;