int ssl_add_cert_chain(SSL *ssl, CBB *cbb) { if (!ssl_has_certificate(ssl)) { return CBB_add_u24(cbb, 0); } CBB certs; if (!CBB_add_u24_length_prefixed(cbb, &certs)) { goto err; } STACK_OF(CRYPTO_BUFFER) *chain = ssl->cert->chain; for (size_t i = 0; i < sk_CRYPTO_BUFFER_num(chain); i++) { CRYPTO_BUFFER *buffer = sk_CRYPTO_BUFFER_value(chain, i); CBB child; if (!CBB_add_u24_length_prefixed(&certs, &child) || !CBB_add_bytes(&child, CRYPTO_BUFFER_data(buffer), CRYPTO_BUFFER_len(buffer)) || !CBB_flush(&certs)) { goto err; } } return CBB_flush(cbb); err: OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR); return 0; }
int ssl_add_client_CA_list(SSL *ssl, CBB *cbb) { CBB child, name_cbb; if (!CBB_add_u16_length_prefixed(cbb, &child)) { return 0; } STACK_OF(CRYPTO_BUFFER) *names = ssl->client_CA; if (names == NULL) { names = ssl->ctx->client_CA; } if (names == NULL) { return CBB_flush(cbb); } for (size_t i = 0; i < sk_CRYPTO_BUFFER_num(names); i++) { const CRYPTO_BUFFER *name = sk_CRYPTO_BUFFER_value(names, i); if (!CBB_add_u16_length_prefixed(&child, &name_cbb) || !CBB_add_bytes(&name_cbb, CRYPTO_BUFFER_data(name), CRYPTO_BUFFER_len(name))) { return 0; } } return CBB_flush(cbb); }
int RSA_marshal_private_key(CBB *cbb, const RSA *rsa) { const int is_multiprime = sk_RSA_additional_prime_num(rsa->additional_primes) > 0; CBB child; if (!CBB_add_asn1(cbb, &child, CBS_ASN1_SEQUENCE) || !CBB_add_asn1_uint64(&child, is_multiprime ? kVersionMulti : kVersionTwoPrime) || !marshal_integer(&child, rsa->n) || !marshal_integer(&child, rsa->e) || !marshal_integer(&child, rsa->d) || !marshal_integer(&child, rsa->p) || !marshal_integer(&child, rsa->q) || !marshal_integer(&child, rsa->dmp1) || !marshal_integer(&child, rsa->dmq1) || !marshal_integer(&child, rsa->iqmp)) { OPENSSL_PUT_ERROR(RSA, RSA_R_ENCODE_ERROR); return 0; } CBB other_prime_infos; if (is_multiprime) { if (!CBB_add_asn1(&child, &other_prime_infos, CBS_ASN1_SEQUENCE)) { OPENSSL_PUT_ERROR(RSA, RSA_R_ENCODE_ERROR); return 0; } size_t i; for (i = 0; i < sk_RSA_additional_prime_num(rsa->additional_primes); i++) { RSA_additional_prime *ap = sk_RSA_additional_prime_value(rsa->additional_primes, i); CBB other_prime_info; if (!CBB_add_asn1(&other_prime_infos, &other_prime_info, CBS_ASN1_SEQUENCE) || !marshal_integer(&other_prime_info, ap->prime) || !marshal_integer(&other_prime_info, ap->exp) || !marshal_integer(&other_prime_info, ap->coeff) || !CBB_flush(&other_prime_infos)) { OPENSSL_PUT_ERROR(RSA, RSA_R_ENCODE_ERROR); return 0; } } } if (!CBB_flush(cbb)) { OPENSSL_PUT_ERROR(RSA, RSA_R_ENCODE_ERROR); return 0; } return 1; }
static int eckey_priv_encode(CBB *out, const EVP_PKEY *key) { const EC_KEY *ec_key = key->pkey.ec; // Omit the redundant copy of the curve name. This contradicts RFC 5915 but // aligns with PKCS #11. SEC 1 only says they may be omitted if known by other // means. Both OpenSSL and NSS omit the redundant parameters, so we omit them // as well. unsigned enc_flags = EC_KEY_get_enc_flags(ec_key) | EC_PKEY_NO_PARAMETERS; // See RFC 5915. CBB pkcs8, algorithm, oid, private_key; if (!CBB_add_asn1(out, &pkcs8, CBS_ASN1_SEQUENCE) || !CBB_add_asn1_uint64(&pkcs8, 0 /* version */) || !CBB_add_asn1(&pkcs8, &algorithm, CBS_ASN1_SEQUENCE) || !CBB_add_asn1(&algorithm, &oid, CBS_ASN1_OBJECT) || !CBB_add_bytes(&oid, ec_asn1_meth.oid, ec_asn1_meth.oid_len) || !EC_KEY_marshal_curve_name(&algorithm, EC_KEY_get0_group(ec_key)) || !CBB_add_asn1(&pkcs8, &private_key, CBS_ASN1_OCTETSTRING) || !EC_KEY_marshal_private_key(&private_key, ec_key, enc_flags) || !CBB_flush(out)) { OPENSSL_PUT_ERROR(EVP, EVP_R_ENCODE_ERROR); return 0; } return 1; }
int BN_bn2cbb(CBB *cbb, const BIGNUM *bn) { /* Negative numbers are unsupported. */ if (BN_is_negative(bn)) { OPENSSL_PUT_ERROR(BN, BN_R_NEGATIVE_NUMBER); return 0; } CBB child; if (!CBB_add_asn1(cbb, &child, CBS_ASN1_INTEGER)) { OPENSSL_PUT_ERROR(BN, BN_R_ENCODE_ERROR); return 0; } /* The number must be padded with a leading zero if the high bit would * otherwise be set (or |bn| is zero). */ if (BN_num_bits(bn) % 8 == 0 && !CBB_add_u8(&child, 0x00)) { OPENSSL_PUT_ERROR(BN, BN_R_ENCODE_ERROR); return 0; } uint8_t *out; if (!CBB_add_space(&child, &out, BN_num_bytes(bn))) { OPENSSL_PUT_ERROR(BN, BN_R_ENCODE_ERROR); return 0; } BN_bn2bin(bn, out); if (!CBB_flush(cbb)) { OPENSSL_PUT_ERROR(BN, BN_R_ENCODE_ERROR); return 0; } return 1; }
int ECDSA_SIG_marshal(CBB *cbb, const ECDSA_SIG *sig) { CBB child; if (!CBB_add_asn1(cbb, &child, CBS_ASN1_SEQUENCE) || !BN_marshal_asn1(&child, sig->r) || !BN_marshal_asn1(&child, sig->s) || !CBB_flush(cbb)) { OPENSSL_PUT_ERROR(ECDSA, ECDSA_R_ENCODE_ERROR); return 0; } return 1; }
int RSA_marshal_public_key(CBB *cbb, const RSA *rsa) { CBB child; if (!CBB_add_asn1(cbb, &child, CBS_ASN1_SEQUENCE) || !marshal_integer(&child, rsa->n) || !marshal_integer(&child, rsa->e) || !CBB_flush(cbb)) { OPENSSL_PUT_ERROR(RSA, RSA_R_ENCODE_ERROR); return 0; } return 1; }
int DSA_marshal_parameters(CBB *cbb, const DSA *dsa) { CBB child; if (!CBB_add_asn1(cbb, &child, CBS_ASN1_SEQUENCE) || !marshal_integer(&child, dsa->p) || !marshal_integer(&child, dsa->q) || !marshal_integer(&child, dsa->g) || !CBB_flush(cbb)) { OPENSSL_PUT_ERROR(DSA, DSA_R_ENCODE_ERROR); return 0; } return 1; }
int DSA_marshal_private_key(CBB *cbb, const DSA *dsa) { CBB child; if (!CBB_add_asn1(cbb, &child, CBS_ASN1_SEQUENCE) || !CBB_add_asn1_uint64(&child, 0 /* version */) || !marshal_integer(&child, dsa->p) || !marshal_integer(&child, dsa->q) || !marshal_integer(&child, dsa->g) || !marshal_integer(&child, dsa->pub_key) || !marshal_integer(&child, dsa->priv_key) || !CBB_flush(cbb)) { OPENSSL_PUT_ERROR(DSA, DSA_R_ENCODE_ERROR); return 0; } return 1; }
static int rsa_priv_encode(CBB *out, const EVP_PKEY *key) { CBB pkcs8, algorithm, oid, null, private_key; if (!CBB_add_asn1(out, &pkcs8, CBS_ASN1_SEQUENCE) || !CBB_add_asn1_uint64(&pkcs8, 0 /* version */) || !CBB_add_asn1(&pkcs8, &algorithm, CBS_ASN1_SEQUENCE) || !CBB_add_asn1(&algorithm, &oid, CBS_ASN1_OBJECT) || !CBB_add_bytes(&oid, rsa_asn1_meth.oid, rsa_asn1_meth.oid_len) || !CBB_add_asn1(&algorithm, &null, CBS_ASN1_NULL) || !CBB_add_asn1(&pkcs8, &private_key, CBS_ASN1_OCTETSTRING) || !RSA_marshal_private_key(&private_key, key->pkey.rsa) || !CBB_flush(out)) { OPENSSL_PUT_ERROR(EVP, EVP_R_ENCODE_ERROR); return 0; } return 1; }
static int rsa_pub_encode(CBB *out, const EVP_PKEY *key) { // See RFC 3279, section 2.3.1. CBB spki, algorithm, oid, null, key_bitstring; if (!CBB_add_asn1(out, &spki, CBS_ASN1_SEQUENCE) || !CBB_add_asn1(&spki, &algorithm, CBS_ASN1_SEQUENCE) || !CBB_add_asn1(&algorithm, &oid, CBS_ASN1_OBJECT) || !CBB_add_bytes(&oid, rsa_asn1_meth.oid, rsa_asn1_meth.oid_len) || !CBB_add_asn1(&algorithm, &null, CBS_ASN1_NULL) || !CBB_add_asn1(&spki, &key_bitstring, CBS_ASN1_BITSTRING) || !CBB_add_u8(&key_bitstring, 0 /* padding */) || !RSA_marshal_public_key(&key_bitstring, key->pkey.rsa) || !CBB_flush(out)) { OPENSSL_PUT_ERROR(EVP, EVP_R_ENCODE_ERROR); return 0; } return 1; }
int RSA_marshal_private_key(CBB *cbb, const RSA *rsa) { CBB child; if (!CBB_add_asn1(cbb, &child, CBS_ASN1_SEQUENCE) || !CBB_add_asn1_uint64(&child, kVersionTwoPrime) || !marshal_integer(&child, rsa->n) || !marshal_integer(&child, rsa->e) || !marshal_integer(&child, rsa->d) || !marshal_integer(&child, rsa->p) || !marshal_integer(&child, rsa->q) || !marshal_integer(&child, rsa->dmp1) || !marshal_integer(&child, rsa->dmq1) || !marshal_integer(&child, rsa->iqmp)) { OPENSSL_PUT_ERROR(RSA, RSA_R_ENCODE_ERROR); return 0; } if (!CBB_flush(cbb)) { OPENSSL_PUT_ERROR(RSA, RSA_R_ENCODE_ERROR); return 0; } return 1; }
static int eckey_pub_encode(CBB *out, const EVP_PKEY *key) { const EC_KEY *ec_key = key->pkey.ec; const EC_GROUP *group = EC_KEY_get0_group(ec_key); const EC_POINT *public_key = EC_KEY_get0_public_key(ec_key); // See RFC 5480, section 2. CBB spki, algorithm, oid, key_bitstring; if (!CBB_add_asn1(out, &spki, CBS_ASN1_SEQUENCE) || !CBB_add_asn1(&spki, &algorithm, CBS_ASN1_SEQUENCE) || !CBB_add_asn1(&algorithm, &oid, CBS_ASN1_OBJECT) || !CBB_add_bytes(&oid, ec_asn1_meth.oid, ec_asn1_meth.oid_len) || !EC_KEY_marshal_curve_name(&algorithm, group) || !CBB_add_asn1(&spki, &key_bitstring, CBS_ASN1_BITSTRING) || !CBB_add_u8(&key_bitstring, 0 /* padding */) || !EC_POINT_point2cbb(&key_bitstring, group, public_key, POINT_CONVERSION_UNCOMPRESSED, NULL) || !CBB_flush(out)) { OPENSSL_PUT_ERROR(EVP, EVP_R_ENCODE_ERROR); return 0; } return 1; }
/* cbs_convert_ber reads BER data from |in| and writes DER data to |out|. If * |string_tag| is non-zero, then all elements must match |string_tag| up to the * constructed bit and primitive element bodies are written to |out| without * element headers. This is used when concatenating the fragments of a * constructed string. If |looking_for_eoc| is set then any EOC elements found * will cause the function to return after consuming it. It returns one on * success and zero on error. */ static int cbs_convert_ber(CBS *in, CBB *out, unsigned string_tag, char looking_for_eoc, unsigned depth) { assert(!(string_tag & CBS_ASN1_CONSTRUCTED)); if (depth > kMaxDepth) { return 0; } while (CBS_len(in) > 0) { CBS contents; unsigned tag, child_string_tag = string_tag; size_t header_len; CBB *out_contents, out_contents_storage; if (!CBS_get_any_ber_asn1_element(in, &contents, &tag, &header_len)) { return 0; } if (is_eoc(header_len, &contents)) { return looking_for_eoc; } if (string_tag != 0) { /* This is part of a constructed string. All elements must match * |string_tag| up to the constructed bit and get appended to |out| * without a child element. */ if ((tag & ~CBS_ASN1_CONSTRUCTED) != string_tag) { return 0; } out_contents = out; } else { unsigned out_tag = tag; if ((tag & CBS_ASN1_CONSTRUCTED) && is_string_type(tag)) { /* If a constructed string, clear the constructed bit and inform * children to concatenate bodies. */ out_tag &= ~CBS_ASN1_CONSTRUCTED; child_string_tag = out_tag; } if (!CBB_add_asn1(out, &out_contents_storage, out_tag)) { return 0; } out_contents = &out_contents_storage; } if (CBS_len(&contents) == header_len && header_len > 0 && CBS_data(&contents)[header_len - 1] == 0x80) { /* This is an indefinite length element. */ if (!cbs_convert_ber(in, out_contents, child_string_tag, 1 /* looking for eoc */, depth + 1) || !CBB_flush(out)) { return 0; } continue; } if (!CBS_skip(&contents, header_len)) { return 0; } if (tag & CBS_ASN1_CONSTRUCTED) { /* Recurse into children. */ if (!cbs_convert_ber(&contents, out_contents, child_string_tag, 0 /* not looking for eoc */, depth + 1)) { return 0; } } else { /* Copy primitive contents as-is. */ if (!CBB_add_bytes(out_contents, CBS_data(&contents), CBS_len(&contents))) { return 0; } } if (!CBB_flush(out)) { return 0; } } return looking_for_eoc == 0; }
static enum ssl_hs_wait_t do_send_new_session_ticket(SSL_HANDSHAKE *hs) { /* TLS 1.3 recommends single-use tickets, so issue multiple tickets in case the * client makes several connections before getting a renewal. */ static const int kNumTickets = 2; SSL *const ssl = hs->ssl; /* If the client doesn't accept resumption with PSK_DHE_KE, don't send a * session ticket. */ if (!hs->accept_psk_mode) { hs->tls13_state = state_done; return ssl_hs_ok; } SSL_SESSION *session = ssl->s3->new_session; CBB cbb; CBB_zero(&cbb); for (int i = 0; i < kNumTickets; i++) { if (!RAND_bytes((uint8_t *)&session->ticket_age_add, 4)) { goto err; } CBB body, ticket, extensions; if (!ssl->method->init_message(ssl, &cbb, &body, SSL3_MT_NEW_SESSION_TICKET) || !CBB_add_u32(&body, session->timeout) || !CBB_add_u32(&body, session->ticket_age_add) || !CBB_add_u16_length_prefixed(&body, &ticket) || !ssl_encrypt_ticket(ssl, &ticket, session) || !CBB_add_u16_length_prefixed(&body, &extensions)) { goto err; } if (ssl->ctx->enable_early_data) { session->ticket_max_early_data = kMaxEarlyDataAccepted; CBB early_data_info; if (!CBB_add_u16(&extensions, TLSEXT_TYPE_ticket_early_data_info) || !CBB_add_u16_length_prefixed(&extensions, &early_data_info) || !CBB_add_u32(&early_data_info, session->ticket_max_early_data) || !CBB_flush(&extensions)) { goto err; } } /* Add a fake extension. See draft-davidben-tls-grease-01. */ if (!CBB_add_u16(&extensions, ssl_get_grease_value(ssl, ssl_grease_ticket_extension)) || !CBB_add_u16(&extensions, 0 /* empty */)) { goto err; } if (!ssl_add_message_cbb(ssl, &cbb)) { goto err; } } hs->session_tickets_sent++; hs->tls13_state = state_done; return ssl_hs_flush; err: CBB_cleanup(&cbb); return ssl_hs_error; }
/* cbs_convert_ber reads BER data from |in| and writes DER data to |out|. If * |squash_header| is set then the top-level of elements from |in| will not * have their headers written. This is used when concatenating the fragments of * an indefinite length, primitive value. If |looking_for_eoc| is set then any * EOC elements found will cause the function to return after consuming it. * It returns one on success and zero on error. */ static int cbs_convert_ber(CBS *in, CBB *out, char squash_header, char looking_for_eoc, unsigned depth) { if (depth > kMaxDepth) { return 0; } while (CBS_len(in) > 0) { CBS contents; unsigned tag; size_t header_len; CBB *out_contents, out_contents_storage; if (!CBS_get_any_ber_asn1_element(in, &contents, &tag, &header_len)) { return 0; } out_contents = out; if (CBS_len(&contents) == header_len) { if (is_eoc(header_len, &contents)) { return looking_for_eoc; } if (header_len > 0 && CBS_data(&contents)[header_len - 1] == 0x80) { /* This is an indefinite length element. If it's a SEQUENCE or SET then * we just need to write the out the contents as normal, but with a * concrete length prefix. * * If it's a something else then the contents will be a series of BER * elements of the same type which need to be concatenated. */ const char context_specific = (tag & 0xc0) == 0x80; char squash_child_headers = is_primitive_type(tag); /* This is a hack, but it sufficies to handle NSS's output. If we find * an indefinite length, context-specific tag with a definite, primitive * tag inside it, then we assume that the context-specific tag is * implicit and the tags within are fragments of a primitive type that * need to be concatenated. */ if (context_specific && (tag & CBS_ASN1_CONSTRUCTED)) { CBS in_copy, inner_contents; unsigned inner_tag; size_t inner_header_len; CBS_init(&in_copy, CBS_data(in), CBS_len(in)); if (!CBS_get_any_ber_asn1_element(&in_copy, &inner_contents, &inner_tag, &inner_header_len)) { return 0; } if (CBS_len(&inner_contents) > inner_header_len && is_primitive_type(inner_tag)) { squash_child_headers = 1; } } if (!squash_header) { unsigned out_tag = tag; if (squash_child_headers) { out_tag &= ~CBS_ASN1_CONSTRUCTED; } if (!CBB_add_asn1(out, &out_contents_storage, out_tag)) { return 0; } out_contents = &out_contents_storage; } if (!cbs_convert_ber(in, out_contents, squash_child_headers, 1 /* looking for eoc */, depth + 1)) { return 0; } if (out_contents != out && !CBB_flush(out)) { return 0; } continue; } } if (!squash_header) { if (!CBB_add_asn1(out, &out_contents_storage, tag)) { return 0; } out_contents = &out_contents_storage; } if (!CBS_skip(&contents, header_len)) { return 0; } if (tag & CBS_ASN1_CONSTRUCTED) { if (!cbs_convert_ber(&contents, out_contents, 0 /* don't squash header */, 0 /* not looking for eoc */, depth + 1)) { return 0; } } else { if (!CBB_add_bytes(out_contents, CBS_data(&contents), CBS_len(&contents))) { return 0; } } if (out_contents != out && !CBB_flush(out)) { return 0; } } return looking_for_eoc == 0; }
int tls13_prepare_certificate(SSL_HANDSHAKE *hs) { SSL *const ssl = hs->ssl; CBB cbb, body, certificate_list; if (!ssl->method->init_message(ssl, &cbb, &body, SSL3_MT_CERTIFICATE) || /* The request context is always empty in the handshake. */ !CBB_add_u8(&body, 0) || !CBB_add_u24_length_prefixed(&body, &certificate_list)) { OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR); goto err; } if (!ssl_has_certificate(ssl)) { if (!ssl_complete_message(ssl, &cbb)) { goto err; } return 1; } CERT *cert = ssl->cert; CBB leaf, extensions; if (!CBB_add_u24_length_prefixed(&certificate_list, &leaf) || !ssl_add_cert_to_cbb(&leaf, cert->x509_leaf) || !CBB_add_u16_length_prefixed(&certificate_list, &extensions)) { OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR); goto err; } if (hs->scts_requested && ssl->ctx->signed_cert_timestamp_list_length != 0) { CBB contents; if (!CBB_add_u16(&extensions, TLSEXT_TYPE_certificate_timestamp) || !CBB_add_u16_length_prefixed(&extensions, &contents) || !CBB_add_bytes(&contents, ssl->ctx->signed_cert_timestamp_list, ssl->ctx->signed_cert_timestamp_list_length) || !CBB_flush(&extensions)) { OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR); goto err; } } if (hs->ocsp_stapling_requested && ssl->ocsp_response != NULL) { CBB contents, ocsp_response; if (!CBB_add_u16(&extensions, TLSEXT_TYPE_status_request) || !CBB_add_u16_length_prefixed(&extensions, &contents) || !CBB_add_u8(&contents, TLSEXT_STATUSTYPE_ocsp) || !CBB_add_u24_length_prefixed(&contents, &ocsp_response) || !CBB_add_bytes(&ocsp_response, CRYPTO_BUFFER_data(ssl->ocsp_response), CRYPTO_BUFFER_len(ssl->ocsp_response)) || !CBB_flush(&extensions)) { OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR); goto err; } } for (size_t i = 0; i < sk_X509_num(cert->x509_chain); i++) { CBB child; if (!CBB_add_u24_length_prefixed(&certificate_list, &child) || !ssl_add_cert_to_cbb(&child, sk_X509_value(cert->x509_chain, i)) || !CBB_add_u16(&certificate_list, 0 /* no extensions */)) { OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR); goto err; } } if (!ssl_complete_message(ssl, &cbb)) { goto err; } return 1; err: CBB_cleanup(&cbb); return 0; }
static int add_new_session_tickets(SSL_HANDSHAKE *hs) { SSL *const ssl = hs->ssl; /* TLS 1.3 recommends single-use tickets, so issue multiple tickets in case * the client makes several connections before getting a renewal. */ static const int kNumTickets = 2; SSL_SESSION *session = hs->new_session; CBB cbb; CBB_zero(&cbb); /* Rebase the session timestamp so that it is measured from ticket * issuance. */ ssl_session_rebase_time(ssl, session); for (int i = 0; i < kNumTickets; i++) { if (!RAND_bytes((uint8_t *)&session->ticket_age_add, 4)) { goto err; } session->ticket_age_add_valid = 1; CBB body, ticket, extensions; if (!ssl->method->init_message(ssl, &cbb, &body, SSL3_MT_NEW_SESSION_TICKET) || !CBB_add_u32(&body, session->timeout) || !CBB_add_u32(&body, session->ticket_age_add) || !CBB_add_u16_length_prefixed(&body, &ticket) || !ssl_encrypt_ticket(ssl, &ticket, session) || !CBB_add_u16_length_prefixed(&body, &extensions)) { goto err; } if (ssl->cert->enable_early_data) { session->ticket_max_early_data = kMaxEarlyDataAccepted; CBB early_data_info; if (!CBB_add_u16(&extensions, TLSEXT_TYPE_ticket_early_data_info) || !CBB_add_u16_length_prefixed(&extensions, &early_data_info) || !CBB_add_u32(&early_data_info, session->ticket_max_early_data) || !CBB_flush(&extensions)) { goto err; } } /* Add a fake extension. See draft-davidben-tls-grease-01. */ if (!CBB_add_u16(&extensions, ssl_get_grease_value(ssl, ssl_grease_ticket_extension)) || !CBB_add_u16(&extensions, 0 /* empty */)) { goto err; } if (!ssl_add_message_cbb(ssl, &cbb)) { goto err; } } return 1; err: CBB_cleanup(&cbb); return 0; }