const STACK_OF()

in crypto/pkcs8/pkcs8_x509.c [1157:1361]


                      const STACK_OF(X509)* chain, int key_nid, int cert_nid,
                      int iterations, int mac_iterations, int key_type) {
  if (key_nid == 0) {
    key_nid = NID_pbe_WithSHA1And3_Key_TripleDES_CBC;
  }
  if (cert_nid == 0) {
    cert_nid = NID_pbe_WithSHA1And40BitRC2_CBC;
  }
  if (iterations == 0) {
    iterations = PKCS12_DEFAULT_ITER;
  }
  if (mac_iterations == 0) {
    mac_iterations = 1;
  }
  if (// In OpenSSL, this specifies a non-standard Microsoft key usage extension
      // which we do not currently support.
      key_type != 0 ||
      // In OpenSSL, -1 here means to omit the MAC, which we do not
      // currently support. Omitting it is also invalid for a password-based
      // PKCS#12 file.
      mac_iterations < 0 ||
      // Don't encode empty objects.
      (pkey == NULL && cert == NULL && sk_X509_num(chain) == 0)) {
    OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_UNSUPPORTED_OPTIONS);
    return 0;
  }

  // PKCS#12 is a very confusing recursive data format, built out of another
  // recursive data format. Section 5.1 of RFC 7292 describes the encoding
  // algorithm, but there is no clear overview. A quick summary:
  //
  // PKCS#7 defines a ContentInfo structure, which is a overgeneralized typed
  // combinator structure for applying cryptography. We care about two types. A
  // data ContentInfo contains an OCTET STRING and is a leaf node of the
  // combinator tree. An encrypted-data ContentInfo contains encryption
  // parameters (key derivation and encryption) and wraps another ContentInfo,
  // usually data.
  //
  // A PKCS#12 file is a PFX structure (section 4), which contains a single data
  // ContentInfo and a MAC over it. This root ContentInfo is the
  // AuthenticatedSafe and its payload is a SEQUENCE of other ContentInfos, so
  // that different parts of the PKCS#12 file can by differently protected.
  //
  // Each ContentInfo in the AuthenticatedSafe, after undoing all the PKCS#7
  // combinators, has SafeContents payload. A SafeContents is a SEQUENCE of
  // SafeBag. SafeBag is PKCS#12's typed structure, with subtypes such as KeyBag
  // and CertBag. Confusingly, there is a SafeContents bag type which itself
  // recursively contains more SafeBags, but we do not implement this. Bags also
  // can have attributes.
  //
  // The grouping of SafeBags into intermediate ContentInfos does not appear to
  // be significant, except that all SafeBags sharing a ContentInfo have the
  // same level of protection. Additionally, while keys may be encrypted by
  // placing a KeyBag in an encrypted-data ContentInfo, PKCS#12 also defines a
  // key-specific encryption container, PKCS8ShroudedKeyBag, which is used
  // instead.

  // Note that |password| may be NULL to specify no password, rather than the
  // empty string. They are encoded differently in PKCS#12. (One is the empty
  // byte array and the other is NUL-terminated UCS-2.)
  size_t password_len = password != NULL ? strlen(password) : 0;

  uint8_t key_id[EVP_MAX_MD_SIZE];
  unsigned key_id_len = 0;
  if (cert != NULL && pkey != NULL) {
    if (!X509_check_private_key(cert, pkey) ||
        // Matching OpenSSL, use the SHA-1 hash of the certificate as the local
        // key ID. Some PKCS#12 consumers require one to connect the private key
        // and certificate.
        !X509_digest(cert, EVP_sha1(), key_id, &key_id_len)) {
      return 0;
    }
  }

  // See https://tools.ietf.org/html/rfc7292#section-4.
  PKCS12 *ret = NULL;
  CBB cbb, pfx, auth_safe, auth_safe_oid, auth_safe_wrapper, auth_safe_data,
      content_infos;
  if (!CBB_init(&cbb, 0) || !CBB_add_asn1(&cbb, &pfx, CBS_ASN1_SEQUENCE) ||
      !CBB_add_asn1_uint64(&pfx, 3) ||
      // auth_safe is a data ContentInfo.
      !CBB_add_asn1(&pfx, &auth_safe, CBS_ASN1_SEQUENCE) ||
      !CBB_add_asn1(&auth_safe, &auth_safe_oid, CBS_ASN1_OBJECT) ||
      !CBB_add_bytes(&auth_safe_oid, kPKCS7Data, sizeof(kPKCS7Data)) ||
      !CBB_add_asn1(&auth_safe, &auth_safe_wrapper,
                    CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 0) ||
      !CBB_add_asn1(&auth_safe_wrapper, &auth_safe_data,
                    CBS_ASN1_OCTETSTRING) ||
      // See https://tools.ietf.org/html/rfc7292#section-4.1. |auth_safe|'s
      // contains a SEQUENCE of ContentInfos.
      !CBB_add_asn1(&auth_safe_data, &content_infos, CBS_ASN1_SEQUENCE)) {
    goto err;
  }

  // If there are any certificates, place them in CertBags wrapped in a single
  // encrypted ContentInfo.
  if (cert != NULL || sk_X509_num(chain) > 0) {
    if (cert_nid < 0) {
      // Place the certificates in an unencrypted ContentInfo. This could be
      // more compactly-encoded by reusing the same ContentInfo as the key, but
      // OpenSSL does not do this. We keep them separate for consistency. (Keys,
      // even when encrypted, are always placed in unencrypted ContentInfos.
      // PKCS#12 defines bag-level encryption for keys.)
      CBB content_info, oid, wrapper, data;
      if (!CBB_add_asn1(&content_infos, &content_info, CBS_ASN1_SEQUENCE) ||
          !CBB_add_asn1(&content_info, &oid, CBS_ASN1_OBJECT) ||
          !CBB_add_bytes(&oid, kPKCS7Data, sizeof(kPKCS7Data)) ||
          !CBB_add_asn1(&content_info, &wrapper,
                        CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 0) ||
          !CBB_add_asn1(&wrapper, &data, CBS_ASN1_OCTETSTRING) ||
          !add_cert_safe_contents(&data, cert, chain, name, key_id,
                                  key_id_len) ||
          !CBB_flush(&content_infos)) {
        goto err;
      }
    } else {
      CBB plaintext_cbb;
      int ok = CBB_init(&plaintext_cbb, 0) &&
               add_cert_safe_contents(&plaintext_cbb, cert, chain, name, key_id,
                                      key_id_len) &&
               add_encrypted_data(
                   &content_infos, cert_nid, password, password_len, iterations,
                   CBB_data(&plaintext_cbb), CBB_len(&plaintext_cbb));
      CBB_cleanup(&plaintext_cbb);
      if (!ok) {
        goto err;
      }
    }
  }

  // If there is a key, place it in a single KeyBag or PKCS8ShroudedKeyBag
  // wrapped in an unencrypted ContentInfo. (One could also place it in a KeyBag
  // inside an encrypted ContentInfo, but OpenSSL does not do this and some
  // PKCS#12 consumers do not support KeyBags.)
  if (pkey != NULL) {
    CBB content_info, oid, wrapper, data, safe_contents, bag, bag_oid,
        bag_contents;
    if (// Add another data ContentInfo.
        !CBB_add_asn1(&content_infos, &content_info, CBS_ASN1_SEQUENCE) ||
        !CBB_add_asn1(&content_info, &oid, CBS_ASN1_OBJECT) ||
        !CBB_add_bytes(&oid, kPKCS7Data, sizeof(kPKCS7Data)) ||
        !CBB_add_asn1(&content_info, &wrapper,
                      CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 0) ||
        !CBB_add_asn1(&wrapper, &data, CBS_ASN1_OCTETSTRING) ||
        !CBB_add_asn1(&data, &safe_contents, CBS_ASN1_SEQUENCE) ||
        // Add a SafeBag containing a PKCS8ShroudedKeyBag.
        !CBB_add_asn1(&safe_contents, &bag, CBS_ASN1_SEQUENCE) ||
        !CBB_add_asn1(&bag, &bag_oid, CBS_ASN1_OBJECT)) {
      goto err;
    }
    if (key_nid < 0) {
      if (!CBB_add_bytes(&bag_oid, kKeyBag, sizeof(kKeyBag)) ||
          !CBB_add_asn1(&bag, &bag_contents,
                        CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 0) ||
          !EVP_marshal_private_key(&bag_contents, pkey)) {
        goto err;
      }
    } else {
      if (!CBB_add_bytes(&bag_oid, kPKCS8ShroudedKeyBag,
                         sizeof(kPKCS8ShroudedKeyBag)) ||
          !CBB_add_asn1(&bag, &bag_contents,
                        CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 0) ||
          !PKCS8_marshal_encrypted_private_key(
              &bag_contents, key_nid, NULL, password, password_len,
              NULL /* generate a random salt */,
              0 /* use default salt length */, iterations, pkey)) {
        goto err;
      }
    }
    size_t name_len = 0;
    if (name) {
      name_len = strlen(name);
    }
    if (!add_bag_attributes(&bag, name, name_len, key_id, key_id_len) ||
        !CBB_flush(&content_infos)) {
      goto err;
    }
  }

  // Compute the MAC. Match OpenSSL in using SHA-1 as the hash function. The MAC
  // covers |auth_safe_data|.
  // TODO (CryptoAlg-2897): Update the default |md| to SHA-256 to align with
  //                        OpenSSL 3.x.
  const EVP_MD *mac_md = EVP_sha1();
  uint8_t mac_salt[PKCS5_SALT_LEN];
  if (!CBB_flush(&auth_safe_data) ||
      !RAND_bytes(mac_salt, sizeof(mac_salt)) ||
      !pkcs12_gen_and_write_mac(
          &pfx, CBB_data(&auth_safe_data), CBB_len(&auth_safe_data), password,
          password_len, mac_salt, sizeof(mac_salt), mac_iterations, mac_md)) {
    goto err;
  }

  ret = PKCS12_new();
  if (ret == NULL ||
      !CBB_finish(&cbb, &ret->ber_bytes, &ret->ber_len)) {
    OPENSSL_free(ret);
    ret = NULL;
    goto err;
  }

err:
  CBB_cleanup(&cbb);
  return ret;
}