Cannot create OpenSSL S/MIME certificate in PHP - php

I can create S/MIME certificate on command line just fine:
openssl genrsa -out some_cert.key 4096
openssl req -new -key some_cert.key -out some_cert.csr
And then sign the certificate by my own authority:
openssl x509 -req -in some_cert.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out some_cert.crt
And subsequently import it to Thunderbird as a 'People's' certificate.
But - on the very same Linux machine (i.e. the same OpenSSL cnf) - I cannot do that in PHP:
$directory = "/tmp";
$path = "/path/to/authority";
$ca = file_get_contents($path . '/ca.crt');
$cakey = array(file_get_contents($path . '/ca.key'), "authorityKeyPass");
$dn = array(
"countryName" => "UK",
"stateOrProvinceName" => "Scotland",
"localityName" => "Aberdeen",
"organizationName" => "Someorg",
"organizationalUnitName" => "Someunit",
"commonName" => foo#domain.org,
"emailAddress" => foo#domain.org
);
$config = array(
"private_key_bits" => 4096,
"private_key_type" => OPENSSL_KEYTYPE_RSA,
'x509_extensions' => 'v3_ca',
);
// Generate a new private key
$privkey = openssl_pkey_new($config);
// Generate a certificate signing request
$csr = openssl_csr_new($dn, $privkey);
// Sign certificate
$sscert = openssl_csr_sign($csr, $ca, $cakey, 365);
// Export CRT (public key)
openssl_x509_export($sscert, $certout);
// Save to file
file_put_contents('/tmp/serverCASigned.crt', $certout);
File is correctly saved, but I cannot import it to Thunderbird as a 'People's' certificate: No error message, just the import dialogue is closed and certificate isn't imported.
And the size of command-line generated .crt file is different from size of file generated in php...

It indeed was the signing part.
The best solution for it I have found is to use phpseclib:
// Load the library phpseclib
set_include_path(get_include_path() . PATH_SEPARATOR . 'phpseclib');
include_once('File/X509.php');
include_once('Crypt/RSA.php');
// CA Private key
$CAPrivKey = new Crypt_RSA();
$CAPrivKey->setPassword("authorityKeyPass");
$CAPrivKey->loadKey(file_get_contents($path . "/ca.key"));
// CA Authority
$issuer = new File_X509();
$issuer->setPrivateKey($CAPrivKey);
$issuer->loadX509(file_get_contents($path . "/ca.crt"));
// Subject - who will be signed by authority
$subject = new File_X509();
$subject->loadCSR($csrout);
// And sign it
$x509 = new File_X509();
$x509->setStartDate('-1 month');
$x509->setEndDate('+5 year');
$x509->setSerialNumber(mt_rand(1, 2147483647) . mt_rand(1, 2147483647));
$result = $x509->sign($issuer, $subject);
// Save to file
file_put_contents('/tmp/serverCASigned.crt', $x509->saveX509($result));
And all runs fine...

Related

Generated certificate from phpseclib is valid but not recognised by browsers after KEYGEN submit

I'm trying to create a simple PKI infrastucture for internal use, and I want to use the html <keygen> tag.
I know this tag sends an SPKAK to server, wich will have to sign it. Since I can't use exec to launch openssl, and have php 5.5, the only way to process SPKAK is with phpseclib.
This is my code:
<?PHP
if(isset($_POST['key'])){
header('Content-type: application/x-x509-user-cert');
header('Content-disposition: attachment; filename=user.crt');
include('File/X509.php');
$capem = file_get_contents('root-ca.crt');
$subject = new File_X509();
$subject->loadCA($capem);
$subject->loadSPKAC($_POST['key']);
$subject->setDN('CN=Username');
$issuer = new File_X509();
$issuer->loadX509($capem);
$cakey = new Crypt_RSA();
$cakey->setPassword('SECRETPASSWORD');
$cakey->loadKey(file_get_contents('root-ca.key'));
$issuer->setPrivateKey($cakey);
$x509 = new File_X509();
$cert = $x509->sign($issuer, $subject);
$x509->loadX509($cert);
$x509->setExtension('id-ce-keyUsage', array('digitalSignature', 'keyEncipherment'));
$x509->setStartDate('-1 day');
$x509->setEndDate('+ 3 year');
$x509->setSerialNumber('1235', 10);
$cert = $x509->sign($issuer, $x509);
echo $x509->saveX509($cert);
}else{
?>
<form method="POST">
<keygen name="key" keytype="RSA" challenge="ucert">
<button>SEND</button>
</form>
<?PHP
}
?>
The strange thing is that the generated certificate is valid (windows recognises it) but the browser (both Chrome and Firefox in my testings) doesn't recognise it, giving error 201 INVALID CERT, so it's not associated with the private key stored on browser.
What's the correct way to do this?
<?php
include('File/X509.php');
include('Crypt/RSA.php');
// create private key / x.509 cert for stunnel / website
$privKey = new Crypt_RSA();
extract($privKey->createKey());
$privKey->loadKey($privatekey);
$pubKey = new Crypt_RSA();
$pubKey->loadKey($publickey);
$pubKey->setPublicKey();
$subject = new File_X509();
$subject->setDNProp('id-at-organizationName', 'phpseclib demo cert');
$subject->setPublicKey($pubKey);
$issuer = new File_X509();
$issuer->setPrivateKey($privKey);
$issuer->setDN($subject->getDN());
$x509 = new File_X509();
$x509->loadX509($x509->saveX509($x509->sign($issuer, $subject)));
$x509->setExtension('id-ce-keyUsage', array('digitalSignature', 'keyEncipherment', 'dataEncipherment'));
$x509->setExtension('id-ce-extKeyUsage', array('id-kp-serverAuth', 'id-kp-clientAuth'));
$result = $x509->sign($issuer, $x509);
file_put_contents('key.pem', $privKey->getPrivateKey() . "\r\n" . $x509->saveX509($result));
exec('openssl pkcs12 -export -out file.pfx -in key.pem');
I was able to import the resultant file.pfx file into Google Chrome. It shows up now as a "Personal Certificate".

openssl_csr_sign always return false

I use the following script to generate a new signed certificate:
<?php
error_reporting(E_ALL);
// Generate a new private (and public) key pair
$privkey = openssl_pkey_new();
// Generate a certificate signing request
$dn = array(
"countryName" => "US",
"stateOrProvinceName" => "Atlantis",
"localityName" => "NeverEverLand",
"organizationName" => "only me",
"organizationalUnitName" => "blah",
"commonName" => "bleh",
"emailAddress" => "test#test.com"
);
$csr = openssl_csr_new($dn, $privkey);
$cacert = file_get_contents('ca.crt');
echo $cacert . "<BR/>";
echo "<BR/>";
$ca_key = file_get_contents('ca.key');
$cakey = array($ca_key, "mysecretpass");
echo $ca_key . "<BR/>";
echo "<BR/>";
$sscert = openssl_csr_sign($csr, $cacert, $cakey, 365);
var_dump($sscert);
echo "<BR/>";
echo "<BR/>";
openssl_pkey_export($privkey, $pkeyout, "mypassword"); var_dump($pkeyout);
echo "<BR/>";
echo "<BR/>";
openssl_csr_export($csr, $csrout); var_dump($csrout);
echo "<BR/>";
echo "<BR/>";
openssl_x509_export($sscert, $certout); var_dump($certout);
echo "<BR/>";
echo "<BR/>";
while (($e = openssl_error_string()) !== false) {
echo $e . "\n";
}
?>
As you can see in the output it can read ca.crt and ca.key. The pass is also correct (changed in above source).
This is the output of the script:
-----BEGIN CERTIFICATE----- MIIFGDCCAwACCQCO584jngEQdjANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJV ... XZ8YaIOkiV4pEiR5 -----END CERTIFICATE-----
-----BEGIN RSA PRIVATE KEY----- Proc-Type: 4,ENCRYPTED DEK-Info: DES-EDE3-CBC,E616BBF003C1FA9D ... VCPNIOlGzmKUvDn0iMKE0KRmN8o3ip8oy4HKPZmuh4h+qznZdNF/pBTurqcNVN/P -----END RSA PRIVATE KEY-----
bool(false)
string(1834) "-----BEGIN ENCRYPTED PRIVATE KEY----- MIIFDjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQIBX0RFXQVx+ICAggA Bfw= -----END ENCRYPTED PRIVATE KEY----- "
string(1045) "-----BEGIN CERTIFICATE REQUEST----- MIICzDCCAbQCAQAwgYYxCzAJBgNVBAYTAlVTMREwDwYDVQQIDAhBdGxhbnRpczEW zZL71nx/8MgG8hyg63vRRJewb/cCIt1q9A4SwGB9iDe75CbR3ij3jHMftXUfvYhV -----END CERTIFICATE REQUEST----- "
NULL
so the command
openssl_csr_sign($csr, $cacert, $cakey, 365);
returns FALSE, even if all input parameters are valid.
Using CentOS 6.4 / Apache/2.2.15 (CentOS) / mod_ssl/2.2.15

How do I set extKeyUsage with phpseclib?

I want to add the SSL Server and SSL Client flags to a cert that I am signing with phpseclib, how would I go about this? I found the setExtension function, but I dont know how to use it. Any help is appreciated, thanks.
I have tried the following and it doesnt work (mostly from the phpseclib example):
// create private key for CA cert
$CAPrivKey = new Crypt_RSA();
extract($CAPrivKey->createKey());
$CAPrivKey->loadKey($privatekey);
$pubKey = new Crypt_RSA();
$pubKey->loadKey($publickey);
$pubKey->setPublicKey();
echo "the private key for the CA cert (can be discarded):\r\n\r\n";
echo $privatekey;
echo "\r\n\r\n";
// create a self-signed cert that'll serve as the CA
$subject = new File_X509();
$subject->setPublicKey($pubKey);
$subject->setDNProp('id-at-organizationName', 'phpseclib demo CA');
$issuer = new File_X509();
$issuer->setPrivateKey($CAPrivKey);
$issuer->setDN($CASubject = $subject->getDN());
$x509 = new File_X509();
$x509->setStartDate('-1 month');
$x509->setEndDate('+1 year');
$x509->setSerialNumber(chr(1));
$x509->makeCA();
$result = $x509->sign($issuer, $subject);
echo "the CA cert to be imported into the browser is as follows:\r\n\r\n";
echo $x509->saveX509($result);
echo "\r\n\r\n";
// create private key / x.509 cert for stunnel / website
$privKey = new Crypt_RSA();
extract($privKey->createKey());
$privKey->loadKey($privatekey);
$pubKey = new Crypt_RSA();
$pubKey->loadKey($publickey);
$pubKey->setPublicKey();
$subject = new File_X509();
$subject->setPublicKey($pubKey);
$subject->setDNProp('id-at-organizationName', 'phpseclib demo cert');
$subject->setDomain('www.google.com');
$issuer = new File_X509();
$issuer->setPrivateKey($CAPrivKey);
$issuer->setDN($CASubject);
$x509 = new File_X509();
$x509->setStartDate('-1 month');
$x509->setEndDate('+1 year');
$x509->setSerialNumber(chr(1));
$x509->setExtension('id-ce-extKeyUsage', array('id-kp-serverAuth', 'id-kp-clientAuth'));
$result = $x509->sign($issuer, $subject);
echo "the stunnel.pem contents are as follows:\r\n\r\n";
echo $privKey->getPrivateKey();
echo "\r\n";
echo $x509->saveX509($result);
echo "\r\n";
What you'd have to currently do is first create the X.509 cert, add the extensions to the X.509 cert after it's been created and then re-sign it. eg.
After $result = $x509->sign($issuer, $subject); do this:
$x509->loadX509($result);
$x509->setExtension('id-ce-extKeyUsage', array('id-kp-serverAuth', 'id-kp-clientAuth'));
$result = $x509->sign($issuer, $x509);
ie. you sign the cert, load it, set the extension, and then resign it.
Not an elegant solution unfortunately. It's my understanding that the API will, at some point, be updated to let you update extensions without having to first have the cert but that has yet to happen.

Why is phpseclib producing incompatible certs?

Why is it that when I try to use a certificate/key pair generated from phpseclib, the OpenSSL server code errors out? Certs/Keys generated from OpenSSL work fine. How do I fix this?
Certificate/Key Generation taken straight from phpseclib documentation:
<?php
include('File/X509.php');
include('Crypt/RSA.php');
// create private key / x.509 cert for stunnel / website
$privKey = new Crypt_RSA();
extract($privKey->createKey());
$privKey->loadKey($privatekey);
$pubKey = new Crypt_RSA();
$pubKey->loadKey($publickey);
$pubKey->setPublicKey();
$subject = new File_X509();
$subject->setDNProp('id-at-organizationName', 'phpseclib demo cert');
//$subject->removeDNProp('id-at-organizationName');
$subject->setPublicKey($pubKey);
$issuer = new File_X509();
$issuer->setPrivateKey($privKey);
$issuer->setDN($subject->getDN());
$x509 = new File_X509();
//$x509->setStartDate('-1 month'); // default: now
//$x509->setEndDate('+1 year'); // default: +1 year
$result = $x509->sign($issuer, $subject);
echo "the stunnel.pem contents are as follows:\r\n\r\n";
echo $privKey->getPrivateKey();
echo "\r\n";
echo $x509->saveX509($result);
echo "\r\n";
?>
Phpseclib requires you to call $x509->setSerialNumber("0") otherwise it will produce an invalid cert.

Openssl return status in cmd

I am using openssl command for creating certificate using PHP.I am using exec.When I give exec the command ,the return status is 1,I echoed whatever i insert in exec(OpenSSL statement) and try it through cmd and it works fine (generate certificates) but it doesn't work when I run it through PHP exec. Anyone who can help me for this.
Thanks
Maybe you'd have an easier time with phpseclib, a pure PHP X.509 implementation? Example of how to create a self-signed cert:
<?php
include('File/X509.php');
include('Crypt/RSA.php');
// create private key / x.509 cert for stunnel / website
$privKey = new Crypt_RSA();
extract($privKey->createKey());
$privKey->loadKey($privatekey);
$pubKey = new Crypt_RSA();
$pubKey->loadKey($publickey);
$pubKey->setPublicKey();
$subject = new File_X509();
$subject->setDNProp('id-at-organizationName', 'phpseclib demo cert');
//$subject->removeDNProp('id-at-organizationName');
$subject->setPublicKey($pubKey);
$issuer = new File_X509();
$issuer->setPrivateKey($privKey);
$issuer->setDN($subject->getDN());
$x509 = new File_X509();
//$x509->setStartDate('-1 month'); // default: now
//$x509->setEndDate('+1 year'); // default: +1 year
$result = $x509->sign($issuer, $subject);
echo "the stunnel.pem contents are as follows:\r\n\r\n";
echo $privKey->getPrivateKey();
echo "\r\n";
echo $x509->saveX509($result);
echo "\r\n";
?>

Categories