int ASN1_mbstring_ncopy()

in Sources/CCryptoBoringSSL/crypto/asn1/a_mbstr.c [88:284]


int ASN1_mbstring_ncopy(ASN1_STRING **out, const unsigned char *in, int len,
                        int inform, unsigned long mask,
                        long minsize, long maxsize)
{
    int str_type;
    char free_out;
    ASN1_STRING *dest;
    size_t nchar = 0;
    char strbuf[32];
    if (len == -1)
        len = strlen((const char *)in);
    if (!mask)
        mask = DIRSTRING_TYPE;

    int (*decode_func)(CBS *, uint32_t*);
    int error;
    switch (inform) {
    case MBSTRING_BMP:
        decode_func = cbs_get_ucs2_be;
        error = ASN1_R_INVALID_BMPSTRING;
        break;

    case MBSTRING_UNIV:
        decode_func = cbs_get_utf32_be;
        error = ASN1_R_INVALID_UNIVERSALSTRING;
        break;

    case MBSTRING_UTF8:
        decode_func = cbs_get_utf8;
        error = ASN1_R_INVALID_UTF8STRING;
        break;

    case MBSTRING_ASC:
        decode_func = cbs_get_latin1;
        error = ERR_R_INTERNAL_ERROR;  // Latin-1 inputs are never invalid.
        break;

    default:
        OPENSSL_PUT_ERROR(ASN1, ASN1_R_UNKNOWN_FORMAT);
        return -1;
    }

    /* Check |minsize| and |maxsize| and work out the minimal type, if any. */
    CBS cbs;
    CBS_init(&cbs, in, len);
    size_t utf8_len = 0;
    while (CBS_len(&cbs) != 0) {
        uint32_t c;
        if (!decode_func(&cbs, &c)) {
            OPENSSL_PUT_ERROR(ASN1, error);
            return -1;
        }
        if (nchar == 0 &&
            (inform == MBSTRING_BMP || inform == MBSTRING_UNIV) &&
            c == 0xfeff) {
            /* Reject byte-order mark. We could drop it but that would mean
             * adding ambiguity around whether a BOM was included or not when
             * matching strings.
             *
             * For a little-endian UCS-2 string, the BOM will appear as 0xfffe
             * and will be rejected as noncharacter, below. */
            OPENSSL_PUT_ERROR(ASN1, ASN1_R_ILLEGAL_CHARACTERS);
            return -1;
        }

        /* Update which output formats are still possible. */
        if ((mask & B_ASN1_PRINTABLESTRING) && !asn1_is_printable(c)) {
            mask &= ~B_ASN1_PRINTABLESTRING;
        }
        if ((mask & B_ASN1_IA5STRING) && (c > 127)) {
            mask &= ~B_ASN1_IA5STRING;
        }
        if ((mask & B_ASN1_T61STRING) && (c > 0xff)) {
            mask &= ~B_ASN1_T61STRING;
        }
        if ((mask & B_ASN1_BMPSTRING) && (c > 0xffff)) {
            mask &= ~B_ASN1_BMPSTRING;
        }
        if (!mask) {
            OPENSSL_PUT_ERROR(ASN1, ASN1_R_ILLEGAL_CHARACTERS);
            return -1;
        }

        nchar++;
        utf8_len += cbb_get_utf8_len(c);
    }

    if (minsize > 0 && nchar < (size_t)minsize) {
        OPENSSL_PUT_ERROR(ASN1, ASN1_R_STRING_TOO_SHORT);
        BIO_snprintf(strbuf, sizeof strbuf, "%ld", minsize);
        ERR_add_error_data(2, "minsize=", strbuf);
        return -1;
    }

    if (maxsize > 0 && nchar > (size_t)maxsize) {
        OPENSSL_PUT_ERROR(ASN1, ASN1_R_STRING_TOO_LONG);
        BIO_snprintf(strbuf, sizeof strbuf, "%ld", maxsize);
        ERR_add_error_data(2, "maxsize=", strbuf);
        return -1;
    }

    /* Now work out output format and string type */
    int (*encode_func)(CBB *, uint32_t) = cbb_add_latin1;
    size_t size_estimate = nchar;
    int outform = MBSTRING_ASC;
    if (mask & B_ASN1_PRINTABLESTRING) {
        str_type = V_ASN1_PRINTABLESTRING;
    } else if (mask & B_ASN1_IA5STRING) {
        str_type = V_ASN1_IA5STRING;
    } else if (mask & B_ASN1_T61STRING) {
        str_type = V_ASN1_T61STRING;
    } else if (mask & B_ASN1_BMPSTRING) {
        str_type = V_ASN1_BMPSTRING;
        outform = MBSTRING_BMP;
        encode_func = cbb_add_ucs2_be;
        size_estimate = 2 * nchar;
    } else if (mask & B_ASN1_UNIVERSALSTRING) {
        str_type = V_ASN1_UNIVERSALSTRING;
        encode_func = cbb_add_utf32_be;
        size_estimate = 4 * nchar;
        outform = MBSTRING_UNIV;
    } else if (mask & B_ASN1_UTF8STRING) {
        str_type = V_ASN1_UTF8STRING;
        outform = MBSTRING_UTF8;
        encode_func = cbb_add_utf8;
        size_estimate = utf8_len;
    } else {
        OPENSSL_PUT_ERROR(ASN1, ASN1_R_ILLEGAL_CHARACTERS);
        return -1;
    }

    if (!out)
        return str_type;
    if (*out) {
        free_out = 0;
        dest = *out;
        if (dest->data) {
            dest->length = 0;
            OPENSSL_free(dest->data);
            dest->data = NULL;
        }
        dest->type = str_type;
    } else {
        free_out = 1;
        dest = ASN1_STRING_type_new(str_type);
        if (!dest) {
            OPENSSL_PUT_ERROR(ASN1, ERR_R_MALLOC_FAILURE);
            return -1;
        }
        *out = dest;
    }

    /* If both the same type just copy across */
    if (inform == outform) {
        if (!ASN1_STRING_set(dest, in, len)) {
            OPENSSL_PUT_ERROR(ASN1, ERR_R_MALLOC_FAILURE);
            return -1;
        }
        return str_type;
    }

    CBB cbb;
    if (!CBB_init(&cbb, size_estimate + 1)) {
        OPENSSL_PUT_ERROR(ASN1, ERR_R_MALLOC_FAILURE);
        goto err;
    }
    CBS_init(&cbs, in, len);
    while (CBS_len(&cbs) != 0) {
        uint32_t c;
        if (!decode_func(&cbs, &c) ||
            !encode_func(&cbb, c)) {
            OPENSSL_PUT_ERROR(ASN1, ERR_R_INTERNAL_ERROR);
            goto err;
        }
    }
    uint8_t *data = NULL;
    size_t data_len;
    if (/* OpenSSL historically NUL-terminated this value with a single byte,
         * even for |MBSTRING_BMP| and |MBSTRING_UNIV|. */
        !CBB_add_u8(&cbb, 0) ||
        !CBB_finish(&cbb, &data, &data_len) ||
        data_len < 1 ||
        data_len > INT_MAX) {
        OPENSSL_PUT_ERROR(ASN1, ERR_R_INTERNAL_ERROR);
        OPENSSL_free(data);
        goto err;
    }
    dest->length = (int)(data_len - 1);
    dest->data = data;
    return str_type;

 err:
    if (free_out)
        ASN1_STRING_free(dest);
    CBB_cleanup(&cbb);
    return -1;
}