certctl list/untrusted truncated any short name containing a literal
'=' -- e.g. a commonName like "Partner=ACME Corp CA", or a fallback
RDN value like "Finance=Division" -- because list_certs() located the
display name by searching for the last '=' in the string and printing
only what followed it. That heuristic assumed '=' only ever appeared
as a DN delimiter, which does not hold for attribute values that
contain '=' themselves.
Separately, read_cert()'s commonName extraction via
X509_NAME_get_text_by_NID() copied the underlying ASN.1 string's raw
bytes verbatim, with no transcoding. For any non-ASCII encoding other
than UTF8String (e.g. BMPString, used by some older or non-US CAs),
this produced garbage, and for a well-formed BMPString specifically,
it produced nothing at all: the first two bytes of any BMP character
encoded as big-endian UTF-16 are 0x00 followed by the character's low
byte, and that leading 0x00 is read as an immediate C string
terminator.
Fix both by deriving every displayed name directly from the subject's
X509_NAME via structured accessors, transcoding each value through
ASN1_STRING_to_UTF8() rather than copying bytes or splitting strings:
- shortname(): the commonName if present, else the value of the last attribute-value pair, else empty. Replaces the old strrchr(cert->name, '=') heuristic entirely, so a '=' inside an attribute's own value can no longer be mistaken for a delimiter.
- format_dn(): the full subject via X509_NAME_print_ex() with XN_FLAG_ONELINE, minus ASN1_STRFLGS_ESC_MSB so that non-ASCII text renders as literal UTF-8 rather than "\xx"-escaped bytes, consistent with shortname(). This replaces X509_NAME_oneline(), which has the same raw-byte-copy problem as X509_NAME_get_text_by_NID() and would render a BMPString value as a hex dump of its UTF-16BE encoding rather than as text. Output format ("type = value", comma- separated) differs from X509_NAME_oneline()'s "/type=value/..." style; no public OpenSSL API reproduces that exact format with correct transcoding, so this is an intentional, visible change to -l's output.
These two functions are now the single source for every displayed
certificate name, so list, untrusted, rehash, trust, and untrust all
show the same short name or full DN for a given certificate, instead
of each command's display depending on which of several inconsistent
code paths happened to compute it.
Since every caller now derives its display string on demand from
cert->x509, struct cert's cached name field -- previously computed
once in read_cert() and reused as-is by list_certs(), the -v trace,
and the trust/untrust warnings -- is removed. This also fixes a
latent inconsistency: -v's trace in read_cert() used to print the
cached name unconditionally, regardless of -l, so it never actually
distinguished short and long output the way list_certs() does; it now
calls shortname()/format_dn() based on -l like list_certs() does.
-l was previously only documented (and only checked by list/
untrusted) for those two commands, but longnames was never actually
scoped to them -- rehash/trust/untrust's shared read_cert() call path
reads it too. Document -l for rehash/trust/untrust in both usage()
and certctl(8), rather than leaving an already-functional flag
undocumented.
The trust/untrust "was previously untrusted/trusted" warnings keep
printing the full subject unconditionally (via format_dn()), since
-l is not meaningful for those two commands on its own and the
warning is a rare, security-relevant notice where the complete
subject is more useful than a short name.
MFC after: 1 week
MFC to: stable/15