static int generate_v3()

in crypto/x509/asn1_gen.c [190:561]


static int generate_v3(CBB *cbb, const char *str, const X509V3_CTX *cnf,
                       CBS_ASN1_TAG tag, int format, int depth) {
  assert((tag & CBS_ASN1_CONSTRUCTED) == 0);
  if (depth > ASN1_GEN_MAX_DEPTH) {
    OPENSSL_PUT_ERROR(ASN1, ASN1_R_ILLEGAL_NESTED_TAGGING);
    return 0;
  }

  // Process modifiers. This function uses a mix of NUL-terminated strings and
  // |CBS|. Several functions only work with NUL-terminated strings, so we need
  // to keep track of when a slice spans the whole buffer.
  for (;;) {
    // Skip whitespace.
    while (*str != '\0' && OPENSSL_isspace((unsigned char)*str)) {
      str++;
    }

    // Modifiers end at commas.
    const char *comma = strchr(str, ',');
    if (comma == NULL) {
      break;
    }

    // Remove trailing whitespace.
    CBS modifier;
    CBS_init(&modifier, (const uint8_t *)str, comma - str);
    for (;;) {
      uint8_t v;
      CBS copy = modifier;
      if (!CBS_get_last_u8(&copy, &v) || !OPENSSL_isspace(v)) {
        break;
      }
      modifier = copy;
    }

    // Advance the string past the modifier, but save the original value. We
    // will need to rewind if this is not a recognized modifier.
    const char *str_old = str;
    str = comma + 1;

    // Each modifier is either NAME:VALUE or NAME.
    CBS name;
    int has_value = CBS_get_until_first(&modifier, &name, ':');
    if (has_value) {
      CBS_skip(&modifier, 1);  // Skip the colon.
    } else {
      name = modifier;
      CBS_init(&modifier, NULL, 0);
    }

    if (cbs_str_equal(&name, "FORMAT") || cbs_str_equal(&name, "FORM")) {
      if (cbs_str_equal(&modifier, "ASCII")) {
        format = ASN1_GEN_FORMAT_ASCII;
      } else if (cbs_str_equal(&modifier, "UTF8")) {
        format = ASN1_GEN_FORMAT_UTF8;
      } else if (cbs_str_equal(&modifier, "HEX")) {
        format = ASN1_GEN_FORMAT_HEX;
      } else if (cbs_str_equal(&modifier, "BITLIST")) {
        format = ASN1_GEN_FORMAT_BITLIST;
      } else {
        OPENSSL_PUT_ERROR(ASN1, ASN1_R_UNKNOWN_FORMAT);
        return 0;
      }
    } else if (cbs_str_equal(&name, "IMP") ||
               cbs_str_equal(&name, "IMPLICIT")) {
      if (tag != 0) {
        OPENSSL_PUT_ERROR(ASN1, ASN1_R_ILLEGAL_NESTED_TAGGING);
        return 0;
      }
      tag = parse_tag(&modifier);
      if (tag == 0) {
        return 0;
      }
    } else if (cbs_str_equal(&name, "EXP") ||
               cbs_str_equal(&name, "EXPLICIT")) {
      // It would actually be supportable, but OpenSSL does not allow wrapping
      // an explicit tag in an implicit tag.
      if (tag != 0) {
        OPENSSL_PUT_ERROR(ASN1, ASN1_R_ILLEGAL_NESTED_TAGGING);
        return 0;
      }
      tag = parse_tag(&modifier);
      return tag != 0 &&
             generate_wrapped(cbb, str, cnf, tag | CBS_ASN1_CONSTRUCTED,
                              /*padding=*/0, format, depth);
    } else if (cbs_str_equal(&name, "OCTWRAP")) {
      tag = tag == 0 ? CBS_ASN1_OCTETSTRING : tag;
      return generate_wrapped(cbb, str, cnf, tag, /*padding=*/0, format, depth);
    } else if (cbs_str_equal(&name, "BITWRAP")) {
      tag = tag == 0 ? CBS_ASN1_BITSTRING : tag;
      return generate_wrapped(cbb, str, cnf, tag, /*padding=*/1, format, depth);
    } else if (cbs_str_equal(&name, "SEQWRAP")) {
      tag = tag == 0 ? CBS_ASN1_SEQUENCE : (tag | CBS_ASN1_CONSTRUCTED);
      tag |= CBS_ASN1_CONSTRUCTED;
      return generate_wrapped(cbb, str, cnf, tag, /*padding=*/0, format, depth);
    } else if (cbs_str_equal(&name, "SETWRAP")) {
      tag = tag == 0 ? CBS_ASN1_SET : (tag | CBS_ASN1_CONSTRUCTED);
      return generate_wrapped(cbb, str, cnf, tag, /*padding=*/0, format, depth);
    } else {
      // If this was not a recognized modifier, rewind |str| to before splitting
      // on the comma. The type itself consumes all remaining input.
      str = str_old;
      break;
    }
  }

  // The final element is, like modifiers, NAME:VALUE or NAME, but VALUE spans
  // the length of the string, including any commas.
  const char *colon = strchr(str, ':');
  CBS name;
  const char *value;
  int has_value = colon != NULL;
  if (has_value) {
    CBS_init(&name, (const uint8_t *)str, colon - str);
    value = colon + 1;
  } else {
    CBS_init(&name, (const uint8_t *)str, strlen(str));
    value = "";  // Most types treat missing and empty value equivalently.
  }

  static const struct {
    const char *name;
    CBS_ASN1_TAG type;
  } kTypes[] = {
      {"BOOL", CBS_ASN1_BOOLEAN},
      {"BOOLEAN", CBS_ASN1_BOOLEAN},
      {"NULL", CBS_ASN1_NULL},
      {"INT", CBS_ASN1_INTEGER},
      {"INTEGER", CBS_ASN1_INTEGER},
      {"ENUM", CBS_ASN1_ENUMERATED},
      {"ENUMERATED", CBS_ASN1_ENUMERATED},
      {"OID", CBS_ASN1_OBJECT},
      {"OBJECT", CBS_ASN1_OBJECT},
      {"UTCTIME", CBS_ASN1_UTCTIME},
      {"UTC", CBS_ASN1_UTCTIME},
      {"GENERALIZEDTIME", CBS_ASN1_GENERALIZEDTIME},
      {"GENTIME", CBS_ASN1_GENERALIZEDTIME},
      {"OCT", CBS_ASN1_OCTETSTRING},
      {"OCTETSTRING", CBS_ASN1_OCTETSTRING},
      {"BITSTR", CBS_ASN1_BITSTRING},
      {"BITSTRING", CBS_ASN1_BITSTRING},
      {"UNIVERSALSTRING", CBS_ASN1_UNIVERSALSTRING},
      {"UNIV", CBS_ASN1_UNIVERSALSTRING},
      {"IA5", CBS_ASN1_IA5STRING},
      {"IA5STRING", CBS_ASN1_IA5STRING},
      {"UTF8", CBS_ASN1_UTF8STRING},
      {"UTF8String", CBS_ASN1_UTF8STRING},
      {"BMP", CBS_ASN1_BMPSTRING},
      {"BMPSTRING", CBS_ASN1_BMPSTRING},
      {"PRINTABLESTRING", CBS_ASN1_PRINTABLESTRING},
      {"PRINTABLE", CBS_ASN1_PRINTABLESTRING},
      {"T61", CBS_ASN1_T61STRING},
      {"T61STRING", CBS_ASN1_T61STRING},
      {"TELETEXSTRING", CBS_ASN1_T61STRING},
      {"SEQUENCE", CBS_ASN1_SEQUENCE},
      {"SEQ", CBS_ASN1_SEQUENCE},
      {"SET", CBS_ASN1_SET},
  };
  CBS_ASN1_TAG type = 0;
  for (size_t i = 0; i < OPENSSL_ARRAY_SIZE(kTypes); i++) {
    if (cbs_str_equal(&name, kTypes[i].name)) {
      type = kTypes[i].type;
      break;
    }
  }
  if (type == 0) {
    OPENSSL_PUT_ERROR(ASN1, ASN1_R_UNKNOWN_TAG);
    return 0;
  }

  // If there is an implicit tag, use the constructed bit from the base type.
  tag = tag == 0 ? type : (tag | (type & CBS_ASN1_CONSTRUCTED));
  CBB child;
  if (!CBB_add_asn1(cbb, &child, tag)) {
    return 0;
  }

  switch (type) {
    case CBS_ASN1_NULL:
      if (*value != '\0') {
        OPENSSL_PUT_ERROR(ASN1, ASN1_R_ILLEGAL_NULL_VALUE);
        return 0;
      }
      return CBB_flush(cbb);

    case CBS_ASN1_BOOLEAN: {
      if (format != ASN1_GEN_FORMAT_ASCII) {
        OPENSSL_PUT_ERROR(ASN1, ASN1_R_NOT_ASCII_FORMAT);
        return 0;
      }
      ASN1_BOOLEAN boolean;
      if (!X509V3_bool_from_string(value, &boolean)) {
        OPENSSL_PUT_ERROR(ASN1, ASN1_R_ILLEGAL_BOOLEAN);
        return 0;
      }
      return CBB_add_u8(&child, boolean ? 0xff : 0x00) && CBB_flush(cbb);
    }

    case CBS_ASN1_INTEGER:
    case CBS_ASN1_ENUMERATED: {
      if (format != ASN1_GEN_FORMAT_ASCII) {
        OPENSSL_PUT_ERROR(ASN1, ASN1_R_INTEGER_NOT_ASCII_FORMAT);
        return 0;
      }
      ASN1_INTEGER *obj = s2i_ASN1_INTEGER(NULL, value);
      if (obj == NULL) {
        OPENSSL_PUT_ERROR(ASN1, ASN1_R_ILLEGAL_INTEGER);
        return 0;
      }
      int len = i2c_ASN1_INTEGER(obj, NULL);
      uint8_t *out;
      int ok = len > 0 &&  //
               CBB_add_space(&child, &out, len) &&
               i2c_ASN1_INTEGER(obj, &out) == len &&
               CBB_flush(cbb);
      ASN1_INTEGER_free(obj);
      return ok;
    }

    case CBS_ASN1_OBJECT: {
      if (format != ASN1_GEN_FORMAT_ASCII) {
        OPENSSL_PUT_ERROR(ASN1, ASN1_R_OBJECT_NOT_ASCII_FORMAT);
        return 0;
      }
      ASN1_OBJECT *obj = OBJ_txt2obj(value, /*dont_search_names=*/0);
      if (obj == NULL || obj->length == 0) {
        OPENSSL_PUT_ERROR(ASN1, ASN1_R_ILLEGAL_OBJECT);
        return 0;
      }
      int ok = CBB_add_bytes(&child, obj->data, obj->length) && CBB_flush(cbb);
      ASN1_OBJECT_free(obj);
      return ok;
    }

    case CBS_ASN1_UTCTIME:
    case CBS_ASN1_GENERALIZEDTIME: {
      if (format != ASN1_GEN_FORMAT_ASCII) {
        OPENSSL_PUT_ERROR(ASN1, ASN1_R_TIME_NOT_ASCII_FORMAT);
        return 0;
      }
      CBS value_cbs;
      CBS_init(&value_cbs, (const uint8_t*)value, strlen(value));
      int ok = type == CBS_ASN1_UTCTIME
                   ? CBS_parse_utc_time(&value_cbs, NULL,
                                        /*allow_timezone_offset=*/0)
                   : CBS_parse_generalized_time(&value_cbs, NULL,
                                                /*allow_timezone_offset=*/0);
      if (!ok) {
        OPENSSL_PUT_ERROR(ASN1, ASN1_R_ILLEGAL_TIME_VALUE);
        return 0;
      }
      return CBB_add_bytes(&child, (const uint8_t *)value, strlen(value)) &&
             CBB_flush(cbb);
    }

    case CBS_ASN1_UNIVERSALSTRING:
    case CBS_ASN1_IA5STRING:
    case CBS_ASN1_UTF8STRING:
    case CBS_ASN1_BMPSTRING:
    case CBS_ASN1_PRINTABLESTRING:
    case CBS_ASN1_T61STRING: {
      int encoding;
      if (format == ASN1_GEN_FORMAT_ASCII) {
        encoding = MBSTRING_ASC;
      } else if (format == ASN1_GEN_FORMAT_UTF8) {
        encoding = MBSTRING_UTF8;
      } else {
        OPENSSL_PUT_ERROR(ASN1, ASN1_R_ILLEGAL_FORMAT);
        return 0;
      }

      // |maxsize| is measured in code points, rather than bytes, but pass it in
      // as a loose cap so fuzzers can exit from excessively long inputs
      // earlier. This limit is not load-bearing because |ASN1_mbstring_ncopy|'s
      // output is already linear in the input.
      ASN1_STRING *obj = NULL;
      if (ASN1_mbstring_ncopy(&obj, (const uint8_t *)value, -1, encoding,
                              ASN1_tag2bit(type), /*minsize=*/0,
                              /*maxsize=*/ASN1_GEN_MAX_OUTPUT) <= 0) {
        return 0;
      }
      int ok = CBB_add_bytes(&child, obj->data, obj->length) && CBB_flush(cbb);
      ASN1_STRING_free(obj);
      return ok;
    }

    case CBS_ASN1_BITSTRING:
      if (format == ASN1_GEN_FORMAT_BITLIST) {
        ASN1_BIT_STRING *obj = ASN1_BIT_STRING_new();
        if (obj == NULL) {
          return 0;
        }
        if (!CONF_parse_list(value, ',', 1, bitstr_cb, obj)) {
          OPENSSL_PUT_ERROR(ASN1, ASN1_R_LIST_ERROR);
          ASN1_BIT_STRING_free(obj);
          return 0;
        }
        int len = i2c_ASN1_BIT_STRING(obj, NULL);
        uint8_t *out;
        int ok = len > 0 &&  //
                 CBB_add_space(&child, &out, len) &&
                 i2c_ASN1_BIT_STRING(obj, &out) == len &&  //
                 CBB_flush(cbb);
        ASN1_BIT_STRING_free(obj);
        return ok;
      }

      // The other formats are the same as OCTET STRING, but with the leading
      // zero bytes.
      if (!CBB_add_u8(&child, 0)) {
        return 0;
      }
      OPENSSL_FALLTHROUGH;

    case CBS_ASN1_OCTETSTRING:
      if (format == ASN1_GEN_FORMAT_ASCII) {
        return CBB_add_bytes(&child, (const uint8_t *)value, strlen(value)) &&
               CBB_flush(cbb);
      }
      if (format == ASN1_GEN_FORMAT_HEX) {
        size_t len;
        uint8_t *data = x509v3_hex_to_bytes(value, &len);
        if (data == NULL) {
          OPENSSL_PUT_ERROR(ASN1, ASN1_R_ILLEGAL_HEX);
          return 0;
        }
        int ok = CBB_add_bytes(&child, data, len) && CBB_flush(cbb);
        OPENSSL_free(data);
        return ok;
      }

      OPENSSL_PUT_ERROR(ASN1, ASN1_R_ILLEGAL_BITSTRING_FORMAT);
      return 0;

    case CBS_ASN1_SEQUENCE:
    case CBS_ASN1_SET:
      if (has_value) {
        if (cnf == NULL) {
          OPENSSL_PUT_ERROR(ASN1, ASN1_R_SEQUENCE_OR_SET_NEEDS_CONFIG);
          return 0;
        }
        const STACK_OF(CONF_VALUE) *section = X509V3_get_section(cnf, value);
        if (section == NULL) {
          OPENSSL_PUT_ERROR(ASN1, ASN1_R_SEQUENCE_OR_SET_NEEDS_CONFIG);
          return 0;
        }
        for (size_t i = 0; i < sk_CONF_VALUE_num(section); i++) {
          const CONF_VALUE *conf = sk_CONF_VALUE_value(section, i);
          if (!generate_v3(&child, conf->value, cnf, /*tag=*/0,
                           ASN1_GEN_FORMAT_ASCII, depth + 1)) {
            return 0;
          }
          // This recursive call, by referencing |section|, is the one place
          // where |generate_v3|'s output can be super-linear in the input.
          // Check bounds here.
          if (CBB_len(&child) > ASN1_GEN_MAX_OUTPUT) {
            OPENSSL_PUT_ERROR(ASN1, ASN1_R_TOO_LONG);
            return 0;
          }
        }
      }
      if (type == CBS_ASN1_SET) {
        // The SET type here is a SET OF and must be sorted.
        return CBB_flush_asn1_set_of(&child) && CBB_flush(cbb);
      }
      return CBB_flush(cbb);

    default:
      OPENSSL_PUT_ERROR(ASN1, ERR_R_INTERNAL_ERROR);
      return 0;
  }
}