export chain with openssl_pkcs12_export in PHP

c, openssl, pfx, php, pkcs#12

Solution

This is a bug that has been brought up nearly two months ago.

Thankfully he provides a sample patch for the docs:

$args = array(
               'extracerts' => $CAcert,
               'friendly_name' => 'My signed cert by CA certificate'
              );
openssl_pkcs12_export($signed_csr, $cerificate_out, $private_key_resource, $passphrase, $args);

What is `$CAcert`? Internally it gets passed to a function that takes an array and turns it into a x509 and that function also detects if it's an array of certs or a single cert. Each element should be an x509 Resource if you're passing an array, or $CAcert should be a single resource if you're not passing an array. `openssl_x509_read` is likely what you want to use here as it returns the x509 resource type that's expected in `$CAcert`.

Some people say that keeping docs updated is one of the hardest parts of PHP project. If you're not great with C and want to help PHP get better that's a good place to start.

Problem

Is it possible to export a certificate and private key to a .pfx along with the certificate chain (root cert and/or intermediate), using PHP's `openssl_pkcs12_export()`? UPDATE: I've taken a look at the source for the php openssl extension, and found that `openssl_pkcs12_export()` supports 2 args other than the ones in the documentation, `friendly_name` and `extracerts`. This is from `ext/openssl/openssl.c`, check out lines 1914-1920 (PHP-5.4.0): ``` 1878 /* {{{ proto bool openssl_pkcs12_export(mixed x509, string &out, mixed priv_key, string pass[, array args]) 1879 Creates and exports a PKCS12 to a var */ 1880 PHP_FUNCTION(openssl_pkcs12_export) 1881 { 1882 X509 * cert = NULL; 1883 BIO * bio_out; 1884 PKCS12 * p12 = NULL; 1885 zval * zcert = NULL, *zout = NULL, *zpkey, *args = NULL; 1886 EVP_PKEY *priv_key = NULL; 1887 long certresource, keyresource; 1888 char * pass; 1889 int pass_len; 1890 char * friendly_name = NULL; 1891 zval ** item; 1892 STACK_OF(X509) *ca = NULL; 1893 1894 if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zzzs|a", &zcert, &zout, &zpkey, &pass, &pass_len, &args) == FAILURE) 1895 return; 1896 1897 RETVAL_FALSE; 1898 1899 cert = php_openssl_x509_from_zval(&zcert, 0, &certresource TSRMLS_CC); 1900 if (cert == NULL) { 1901 php_error_docref(NULL TSRMLS_CC, E_WARNING, "cannot get cert from parameter 1"); 1902 return; 1903 } 1904 priv_key = php_openssl_evp_from_zval(&zpkey, 0, "", 1, &keyresource TSRMLS_CC); 1905 if (priv_key == NULL) { 1906 php_error_docref(NULL TSRMLS_CC, E_WARNING, "cannot get private key from parameter 3"); 1907 goto cleanup; 1908 } 1909 if (cert && !X509_check_private_key(cert, priv_key)) { 1910 php_error_docref(NULL TSRMLS_CC, E_WARNING, "private key does not correspond to cert"); 1911 goto cleanup; 1912 } 1913 1914 /* parse extra config from args array, promote this to an extra function */ 1915 if (args && zend_hash_find(Z_ARRVAL_P(args), "friendly_name", sizeof("friendly_name"), (void**)&item) == SUCCESS) 1916 friendly_name = Z_STRVAL_PP(item); 1917 1918 if (args && zend_hash_find(Z_ARRVAL_P(args), "extracerts", sizeof("extracerts"), (void**)&item) == SUCCESS) 1919 ca = php_array_to_X509_sk(item TSRMLS_CC); 1920 /* end parse extra config */ 1921 1922 p12 = PKCS12_create(pass, friendly_name, priv_key, cert, ca, 0, 0, 0, 0, 0); 1923 1924 bio_out = BIO_new(BIO_s_mem()); 1925 if (i2d_PKCS12_bio(bio_out, p12)) { 1926 BUF_MEM *bio_buf; 1927 1928 zval_dtor(zout); 1929 BIO_get_mem_ptr(bio_out, &bio_buf); 1930 ZVAL_STRINGL(zout, bio_buf->data, bio_buf->length, 1); 1931 1932 RETVAL_TRUE; 1933 } 1934 1935 BIO_free(bio_out); 1936 PKCS12_free(p12); 1937 php_sk_X509_free(ca); 1938 1939 cleanup: 1940 1941 if (keyresource == -1 && priv_key) { 1942 EVP_PKEY_free(priv_key); 1943 } 1944 if (certresource == -1 && cert) { 1945 X509_free(cert); 1946 } 1947 } 1948 /* }}} */ ``` however, I'm not quite sure how to pass the additional certificates as arguments... any clues? Let me know if it's easier readable without the line numbers

Original source