Parcourir la source

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>
Jouni Malinen il y a 11 ans
Parent
commit
39b420f7b1
3 fichiers modifiés avec 291 ajouts et 21 suppressions
  1. 10 0
      hs20/client/osu_client.c
  2. 1 0
      src/utils/http-utils.h
  3. 280 21
      src/utils/http_curl.c

+ 10 - 0
hs20/client/osu_client.c

@@ -2945,6 +2945,7 @@ static void usage(void)
 	       "- dl_polupd_ca <PPS> <CA file>\n"
 	       "- dl_aaa_ca <PPS> <CA file>\n"
 	       "- browser <URL>\n"
+	       "- parse_cert <X.509 certificate (DER)>\n"
 	       "- osu_select <OSU info directory> [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]);
 	}

+ 1 - 0
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 */

+ 280 - 21
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,32 +862,72 @@ 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);
+	parse_cert(ctx, &hcert, cert, &names);
+	ret = ctx->cert_cb(ctx->cert_cb_ctx, &hcert);
+	parse_cert_free(&hcert, names);
 
-	add_logotype_ext(ctx, &hcert, cert);
+	return ret;
+}
 
-	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);
+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;
+	}
 
-	for (i = 0; i < hcert.num_othername; i++)
-		os_free(hcert.othername[i].oid);
-	os_free(hcert.othername);
+	cert = d2i_X509_bio(in, NULL);
+	BIO_free(in);
 
-	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);
+	if (cert == NULL) {
+		wpa_printf(MSG_ERROR, "Could not parse certificate");
+		return;
 	}
-	os_free(hcert.logo);
 
-	sk_GENERAL_NAME_pop_free(names, GENERAL_NAME_free);
+	out = BIO_new_fp(stdout, BIO_NOCLOSE);
+	if (out) {
+		X509_print_ex(out, cert, XN_FLAG_COMPAT,
+			      X509_FLAG_COMPAT);
+		BIO_free(out);
+	}
 
-	return ret;
+	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);
 }