ASN1_MBSTRING_COPY(3)

πŸ“¦ version 4.4.0, in alpine 3.24, arch rolling

πŸ’… ASN1_MBSTRING_COPY(3) πŸ’…

okay so this is from the Library Functions Manual bestie, and it's literally all about copying a multibyte string into an ASN.1 string object. seven whole functions, one page, let's go 🫢


✨ NAME ✨

ASN1_mbstring_copy, ASN1_mbstring_ncopy, ASN1_STRING_set_by_NID, ASN1_STRING_set_default_mask, ASN1_STRING_set_default_mask_asc, ASN1_STRING_get_default_mask, ASN1_tag2bit β€” the whole squad whose job is to copy a multibyte string into an ASN.1 string object. that's the vibe. πŸ¦‹


🌸 SYNOPSIS 🌸

library libcrypto

#include <openssl/asn1.h>

int
ASN1_mbstring_copy(ASN1_STRING **out, const unsigned char *in,
    int inbytes, int inform, unsigned long mask);

int
ASN1_mbstring_ncopy(ASN1_STRING **out, const unsigned char *in,
    int inbytes, int inform, unsigned long mask, long minchars,
    long maxchars);

ASN1_STRING *
ASN1_STRING_set_by_NID(ASN1_STRING **out, const unsigned char *in,
    int inbytes, int inform, int nid);

void
ASN1_STRING_set_default_mask(unsigned long mask);

int
ASN1_STRING_set_default_mask_asc(const char *maskname);

unsigned long
ASN1_STRING_get_default_mask(void);

unsigned long
ASN1_tag2bit(int tag);

πŸŽ€ DESCRIPTION πŸŽ€

so ASN1_mbstring_copy() takes inbytes bytes starting at in, treats them as a multibyte string, and copies the whole thing to *out β€” optionally swapping the encoding while it's at it. and if inbytes is negative? no worries babe, it just falls back to the strlen(3) of in instead. iconic behaviour honestly.

the inform argument is literally "hey what encoding is in in", and here's the tea:

  • MBSTRING_ASC β€” ISO-Latin-1
  • MBSTRING_BMP β€” UTF-16
  • MBSTRING_UNIV β€” UTF-32
  • MBSTRING_UTF8 β€” UTF-8

then the bit mask is like, the set of ASN.1 string types you're willing to accept as output. here's the chart bestie:

  • B_ASN1_PRINTABLESTRING β€” gives you ASN1_PRINTABLESTRING β€” default: yes
  • B_ASN1_IA5STRING β€” gives you ASN1_IA5STRING β€” default: no
  • B_ASN1_T61STRING β€” gives you ASN1_T61STRING β€” default: yes
  • B_ASN1_BMPSTRING β€” gives you ASN1_BMPSTRING β€” default: yes
  • B_ASN1_UNIVERSALSTRING β€” gives you ASN1_UNIVERSALSTRING β€” default: no
  • any other bit β€” gives you ASN1_UTF8STRING β€” default: yes

the way it works: the first type from that table that's included in the mask argument and can actually represent in is the one that wins and becomes your output type. and that "default" column? that's just telling you whether the type counts as acceptable when mask has the special value 0. cute right πŸ’–

also these bit mask constants each bundle up several of the bits above, so you don't have to spell them out:

  • B_ASN1_DIRECTORYSTRING β€” PRI yes, IA5 no, T61 yes, BMP yes, UNI yes, UTF8 yes
  • DIRSTRING_TYPE β€” PRI yes, IA5 no, T61 yes, BMP yes, UNI no, UTF8 yes
  • PKCS9STRING_TYPE β€” PRI yes, IA5 yes, T61 yes, BMP yes, UNI no, UTF8 yes

okay now the actual flow, pay attention πŸ‘‘

  • if out is NULL, then inform, inbytes, and in get validated and the output type gets figured out and returned β€” but literally nothing is copied. it's giving window shopping.
  • otherwise, if *out is NULL, a brand new output object of the output type is allocated, and a pointer to it is dropped into *out.
  • otherwise, **out is used as the output object. any data already sitting in it gets freed, and its type gets changed to the output type. out with the old, in with the new πŸ’…
  • finally, in is copied into the output object, changing the character encoding if inform doesn't match whatever encoding the output type uses. slay.

ASN1_mbstring_ncopy() is the same deal, except the number of characters in in gets restricted to the range from minchars to maxchars, inclusive. and if maxchars is 0, there's no upper limit on characters at all β€” unlimited characters, no cap (well, no max cap 😌).

ASN1_STRING_set_by_NID() is also similar, but with these differences, and yes you need all of them:

  • if out is NULL, a new output object is allocated and returned instead of skipping the copying. so no window shopping here, we're actually buying.
  • if nid has a global string table entry that you can retrieve with ASN1_STRING_TABLE_get(3), then mask, minchars, and maxchars are taken from that string table entry. for some values of nid, an additional global mask gets AND'ed into the mask before it's used. the default value of that global mask is B_ASN1_UTF8STRING.
  • if nid has no global string table entry, then B_ASN1_PRINTABLESTRING | B_ASN1_T61STRING | B_ASN1_BMPSTRING | B_ASN1_UTF8STRING is used instead of the mask from the table, and the global mask also gets AND'ed into it. bestie gets the fallback fit.
  • even though success and failure happen in the same situations, the return value is different. ASN1_STRING_type(3) can be used to determine the type of the return value. iykyk.

ASN1_STRING_set_default_mask() sets the global mask used by ASN1_STRING_set_by_NID() to whatever you pass as the mask argument. simple as.

ASN1_STRING_set_default_mask_asc() sets the global mask like this:

  • "default" β€” anything
  • "nombstr" β€” anything except B_ASN1_BMPSTRING | B_ASN1_UTF8STRING
  • "pkix" β€” anything except B_ASN1_T61STRING
  • "utf8only" β€” B_ASN1_UTF8STRING

and if the maskname argument starts with the substring "MASK:", then the rest of it gets interpreted as an unsigned long value using strtoul(3). sneaky but valid ✨

ASN1_tag2bit() translates ASN.1 data types to type bits, and here's the entire roster, no skipping:

  • V_ASN1_BIT_STRING β†’ B_ASN1_BIT_STRING
  • V_ASN1_BMPSTRING β†’ B_ASN1_BMPSTRING
  • V_ASN1_BOOLEAN β†’ 0
  • V_ASN1_ENUMERATED β†’ B_ASN1_UNKNOWN
  • V_ASN1_EOC β†’ 0
  • V_ASN1_EXTERNAL β†’ B_ASN1_UNKNOWN
  • V_ASN1_GENERALIZEDTIME β†’ B_ASN1_GENERALIZEDTIME
  • V_ASN1_GENERALSTRING β†’ B_ASN1_GENERALSTRING
  • V_ASN1_GRAPHICSTRING β†’ B_ASN1_GRAPHICSTRING
  • V_ASN1_IA5STRING β†’ B_ASN1_IA5STRING
  • V_ASN1_INTEGER β†’ 0
  • V_ASN1_ISO64STRING β†’ B_ASN1_ISO64STRING
  • V_ASN1_NULL β†’ 0
  • V_ASN1_NUMERICSTRING β†’ B_ASN1_NUMERICSTRING
  • V_ASN1_OBJECT β†’ 0
  • V_ASN1_OBJECT_DESCRIPTOR β†’ B_ASN1_UNKNOWN
  • V_ASN1_OCTET_STRING β†’ B_ASN1_OCTET_STRING
  • V_ASN1_PRINTABLESTRING β†’ B_ASN1_PRINTABLESTRING
  • V_ASN1_REAL β†’ B_ASN1_UNKNOWN
  • V_ASN1_SEQUENCE β†’ B_ASN1_SEQUENCE
  • V_ASN1_SET β†’ 0
  • V_ASN1_T61STRING β†’ B_ASN1_T61STRING
  • V_ASN1_TELETEXSTRING β†’ B_ASN1_TELETEXSTRING
  • V_ASN1_UNDEF β†’ 0
  • V_ASN1_UNIVERSALSTRING β†’ B_ASN1_UNIVERSALSTRING
  • V_ASN1_UTCTIME β†’ B_ASN1_UTCTIME
  • V_ASN1_UTF8STRING β†’ B_ASN1_UTF8STRING
  • V_ASN1_VIDEOTEXSTRING β†’ B_ASN1_VIDEOTEXSTRING
  • V_ASN1_VISIBLESTRING β†’ B_ASN1_VISIBLESTRING
  • 11, 13, 14, 15, 29 β†’ B_ASN1_UNKNOWN
  • other (< 0, > 30) β†’ 0

and in typical usage? the calling code calculates the bitwise AND of the return value and a mask describing the data types the calling code is willing to use. if the result of that AND operation is non-zero, the data type is adequate β€” and if it's zero, the calling code may need to raise an error. that's the whole vibe check right there πŸ”₯


πŸ¦‹ RETURN VALUES πŸ¦‹

ASN1_mbstring_copy() and ASN1_mbstring_ncopy() return the V_ASN1_* constant representing the output type, or -1 if:

  • inform is invalid,
  • inbytes or in is invalid for the inform encoding,
  • in contains an UTF-16 surrogate, which is unsupported even for input using the UTF-16 encoding (yes, even then 😭),
  • or memory allocation fails.

ASN1_mbstring_ncopy() also returns -1 if in contains fewer than minchars or more than maxchars characters. length restrictions are real.

ASN1_STRING_set_by_NID() returns the new or changed ASN.1 string object, or NULL on failure.

ASN1_STRING_set_default_mask_asc() returns 1 if successful, or 0 if "MASK:" is not followed by a number, if the number is followed by a non-numeric character, or if the maskname is invalid.

ASN1_STRING_get_default_mask() returns the global mask. that's it, that's the whole function, and honestly? queen.

ASN1_tag2bit() returns a B_ASN1_* constant or 0.


🌈 SEE ALSO 🌈

ASN1_PRINTABLE_type(3), ASN1_STRING_new(3), ASN1_STRING_set(3), ASN1_STRING_TABLE_get(3), ASN1_UNIVERSALSTRING_to_string(3) β€” go read those too bestie, they're all connected 🫢


⭐ HISTORY ⭐

ASN1_mbstring_copy(), ASN1_mbstring_ncopy(), ASN1_STRING_set_by_NID(), ASN1_STRING_set_default_mask(), ASN1_STRING_set_default_mask_asc(), and ASN1_STRING_get_default_mask() first appeared in OpenSSL 0.9.5 and have been available since OpenBSD 2.7.

ASN1_tag2bit() first appeared in OpenSSL 0.9.7 and has been available since OpenBSD 3.2. late bloomer, still iconic πŸ’œ


πŸ’€ BUGS πŸ’€

if integer overflow occurs in ASN1_STRING_set_default_mask_asc() while parsing a number following "MASK:", the function succeeds, essentially behaving in the same way as for "default". yes, it just quietly does a whole different thing. no cap, that's the bug.
passing "default" to ASN1_STRING_set_default_mask_asc() does not restore the default mask. instead, passing "utf8only" does that. the way that is so counterintuitive 😭 remember it, it'll save you.

okay that's the whole page diva πŸ’– this baby comes to you courtesy of Debian, dated June 8, 2025 β€” go forth and copy your multibyte strings responsibly, mwah πŸŽ€βœ¨πŸŒΈ

πŸ“¦ where this page comes from