From f6a49b32fda99b726ad5b4434bcade613dccf49e Mon Sep 17 00:00:00 2001 From: tb <> Date: Sun, 21 Dec 2025 07:14:47 +0000 Subject: Reimplement ASN1_PRINTABLE_type() dance in ca.c In ca.c, there is some minimal validation of strings in the subject of a certificate. ASN1_PRINTABLE_type() is silly API that only exists for this one call. I want to remove a_print.c in the next major bump. ASN1_PRINTABLE_type() returns V_ASN1_PRINTABLESTRING if all characters belong to the specific subset of ASCII listed in X.680, 41.4, Table 10. Otherwise it returns V_ASN1_T61STRING or V_ASN1_IA5STRING depending on whether there is a character with the high bit set or not. With this in mind, the logic in ca.c comes down to this: blindly allow UTF-8, BMP and T61 strings. All other strings get rejected if the high bit of some character is set, or if the string is printable and there is a non-printable character. ok jsing kenjiro --- src/usr.bin/openssl/ca.c | 68 +++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 55 insertions(+), 13 deletions(-) (limited to 'src') diff --git a/src/usr.bin/openssl/ca.c b/src/usr.bin/openssl/ca.c index 525596ae7c..a2e8a68368 100644 --- a/src/usr.bin/openssl/ca.c +++ b/src/usr.bin/openssl/ca.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ca.c,v 1.63 2025/11/27 08:24:30 tb Exp $ */ +/* $OpenBSD: ca.c,v 1.64 2025/12/21 07:14:47 tb Exp $ */ /* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) * All rights reserved. * @@ -1652,6 +1652,54 @@ certify_cert(X509 **xret, char *infile, EVP_PKEY *pkey, X509 *x509, return (ok); } +static int +is_printablestring_octet(const uint8_t u8) +{ + /* + * X.680, 41.4, Table 10 lists the allowed characters in this order. + */ + + if (u8 >= 'A' && u8 <= 'Z') + return 1; + if (u8 >= 'a' && u8 <= 'z') + return 1; + if (u8 >= '0' && u8 <= '9') + return 1; + + return u8 == ' ' || u8 == '\'' || u8 == '(' || u8 == ')' || u8 == '+' || + u8 == ',' || u8 == '-' || u8 == '.' || u8 == '/' || u8 == ':' || + u8 == '=' || u8 == '?'; +} + +/* + * Allows the high bit to be set only for UTF8, BMP and T61 strings, and + * checks that a PrintableString only contains the specified characters. + */ +static int +validate_octets(const ASN1_STRING *astr) +{ + const uint8_t *buf = ASN1_STRING_get0_data(astr); + int type = ASN1_STRING_type(astr); + int i; + + if (type == V_ASN1_BMPSTRING || type == V_ASN1_UTF8STRING || + type == V_ASN1_T61STRING) + return 1; + + for (i = 0; i < ASN1_STRING_length(astr); i++) { + if (is_printablestring_octet(buf[i])) + continue; + + if (type == V_ASN1_PRINTABLESTRING) + return 0; + + if ((buf[i] & 0x80) != 0) + return 0; + } + + return 1; +} + static int do_body(X509 **xret, EVP_PKEY *pkey, X509 *x509, const EVP_MD *dgst, STACK_OF(OPENSSL_STRING) *sigopts, STACK_OF(CONF_VALUE) *policy, @@ -1723,18 +1771,12 @@ do_body(X509 **xret, EVP_PKEY *pkey, X509 *x509, const EVP_MD *dgst, "\nemailAddress type needs to be of type IA5STRING\n"); goto err; } - if ((ASN1_STRING_type(str) != V_ASN1_BMPSTRING) && - (ASN1_STRING_type(str) != V_ASN1_UTF8STRING)) { - j = ASN1_PRINTABLE_type(ASN1_STRING_get0_data(str), - ASN1_STRING_length(str)); - if (((j == V_ASN1_T61STRING) && - (ASN1_STRING_type(str) != V_ASN1_T61STRING)) || - ((j == V_ASN1_IA5STRING) && - (ASN1_STRING_type(str) == V_ASN1_PRINTABLESTRING))) { - BIO_printf(bio_err, - "\nThe string contains characters that are illegal for the ASN.1 type\n"); - goto err; - } + + if (!validate_octets(str)) { + BIO_printf(bio_err, + "\nThe string contains characters that are illegal " + "for the ASN.1 type\n"); + goto err; } if (default_op) old_entry_print(bio_err, obj, str); -- cgit v1.2.3-55-g6feb