SSL PHP Server Key and Certificate generation

Server key and certificate generation revisited.

Introduction

Uniform Server keeps increasing in size partly due to core component increases and duplication.

Nothing can be done regarding core components hence this write-up addresses duplication. Server key and certificate generation comes into this category it currently uses components from the OpenSSL distribution. As a side note using the CA plugin introduces yet more duplication.

The OpenSSL distribution is primarily command line driven and suitable for batch files as originally implemented on Uniform Server. However control architecture moved over to a PHP CLI implementation with Server key and certificate generation reaming batch file driven. This tutorial covers converting this batch files implementation to PHP CLI solution.

PHP ships with extension php_openssl.dll and associated support libraries, these provide a vast number of openssl functions of which only four are required to generate a server certificate and key.

Preliminary

If you wish to follow this tutorial or experiment with other openssl functions download any Uniform Server 5.6-Nano version.

In folder UniServer\unicon create a new folder named z_cert

In this folder create two files Run.bat and cert.php with the following content.

Run.bat   cert.php
TITLE UNIFORM SERVER - Certificate and Key generator 
COLOR B0
@echo off
cls
..\..\usr\local\php\php.exe -c ..\..\usr\local\php\php-cli.ini  gen.php
pause
 
<?php
print "\ntest\n";
?>

Double click Run.bat runs script cert.php it produces nothing spectacular other than to display test. It proves you have a working set-up for this tutorial.

  • ..\..\usr\local\php\php.exe - Runs PHP in CLI mode
  • -c ..\..\usr\local\php\php-cli.ini - Informs PHP CLI to use configuration file php-cli.ini
  • gen.php - Name of script to be run by PHP CLI

Top

Errors

If you have read any of my tutorials you will know I hate just providing working examples this tutorial is no exception.

PHP Configuration file

Add the following lines as shown on right to cert.php

Error produced::

Fatal error: Call to undefined function openssl_pkey_new() in C:\Nano_5_6_6\UniServer\unicon\z_cert\gen.php on line 4 Press any key to continue . . .

<?php
print "\ntest\n";
//=== Generate a new private (and public) key pair
$privkey = openssl_pkey_new();
?>

Function openssl_pkey_new() is defined in the openssl library. Problem is extension php_openssl.dll is not being loaded because its not configured in configuration file php-cli.ini

Edit file UniServer\usr\local\php\php-cli.ini

Add the following line:

  • extension=php_openssl.dll As shown on right

Run (Run.bat) script again this time there will be no errors.

[PHP]
extension=php_curl.dll
extension=php_mysql.dll
extension=php_openssl.dll

extension_dir = "./extensions"
error_reporting = E_ALL | E_STRICT
date.timezone = "Europe/London"

Top

Openssl configuration

The above line creates private and public keys used in other function.

Before looking at these in detail add the following code as shown on the right to file cert.php

Run the new script you will receive a warning

Warning:

Warning: openssl_csr_sign(): cannot get CSR from parameter 1 in C:\Nano_5_6_6\UniServer\unicon\z_cert\gen.php on line 21

Although it gives the impression it’s a warning and can be ignored in reality it is fatal.

Parameter 1 ($csr) is a resource that was never created hence the above error.

Reading the manual you will find

Note: You need to have a valid openssl.cnf
installed for this function to operate correctly.

Most functions use this file; trouble is it cannot be found. Path assumed to be either defined by OPENSSL_CONF or SSLEAY_CONF environmental variables or on the default path c:\usr\local\ssl.

<?php
print "\ntest\n";
//=== Generate a new private (and public) key pair
$privkey = openssl_pkey_new();

//=== Create data array for certificate information
$dn = array(
   "countryName"            => "UK",
   "stateOrProvinceName"    => "Cambridge",
   "localityName"           => "Cambs",
   "organizationName"       => "UniServer",
   "organizationalUnitName" => "Demo",
   "commonName"             => "localhost",
   "emailAddress"           => "me@example.com"
);

//=== Generate a certificate signing request
$csr = openssl_csr_new($dn, $privkey);

//== Create a self-signed certificate valid for 365 days
$sscert = openssl_csr_sign($csr, "my secret", $privkey, 365);

?>

Uniform Server is portable hence the above default path is not applicable. Using environmental variables is not always a predictable solution.

Alternative every function that requires access to the openssl.conf file has an input array named $configargs allowing a path to be specified by setting its config key. This is reliable and preferred method covered later.

Top

Open SSL configuration file

Uniform Server already has a pre-configured configuration file copy this to our test folder.

  • Copy file UniServer\unicon\key_cert_gen\openssl.cnf
  • To folder UniServer\unicon\z_cert

Alternatively create a new file named openssl.cnf with the following content:

#######################################################################
# File name: openssl.cnf
# Created By: The Uniform Server Development Team
# Edited Last By: Mike Gleaves (ric) 
# V 1.0 7-1-2009
# Uses OpenSSL 0.9.8i
########################################################################

#
# OpenSSL configuration file.
#

# Establish working directory.
dir			= .

[ req ]
default_bits            = 1024
default_md              = sha1
default_keyfile         = privkey.pem
distinguished_name      = req_distinguished_name
x509_extensions         = v3_ca
string_mask             = nombstr

[ req_distinguished_name ]
countryName             = Country Name (2 letter code)
countryName_min         = 2
countryName_max         = 2
stateOrProvinceName     = State or Province Name (full name)
localityName            = Locality Name (eg, city)
0.organizationName      = Organization Name (eg, company)
organizationalUnitName  = Organizational Unit Name (eg, section)
commonName              = Common Name (eg, YOUR fqdn)
commonName_max          = 64
emailAddress            = Email Address
emailAddress_max        = 64

[ ssl_server ]
basicConstraints        = CA:FALSE
nsCertType              = server
keyUsage                = digitalSignature, keyEncipherment
extendedKeyUsage        = serverAuth, nsSGC, msSGC
nsComment               = "OpenSSL Certificate for SSL Web Server"

[ v3_req ]
basicConstraints = CA:FALSE
keyUsage         = nonRepudiation, digitalSignature, keyEncipherment

[ v3_ca ]
basicConstraints        = critical, CA:true, pathlen:0
nsCertType              = sslCA
keyUsage                = cRLSign, keyCertSign
extendedKeyUsage        = serverAuth, clientAuth
nsComment               = "OpenSSL CA Certificate"

Top

Functions and arrays required

This section looks at functions and arrays required to implement a server certificate and key.

Top

Array configargs

You can use this array instead of a configuration file and set its keys accordingly details on this page

We are interested only in defining a path to our configuration file. First absolute path is calculated and assigned to key.

//== Determine path
$ssl_path = getcwd();
$ssl_path = preg_replace('/\\\/','/', $ssl_path);  // Replace \ with /

//== Create a configuration array containing path to openssl.cnf 
$config = array(
"config" => "$ssl_path/openssl.cnf"
);

Top

Data array dn

Next we create a data array (dn) containing our certificate details.

Change appropriate keys to meet your own requirements:

//=== Create data array for certificate information
$dn = array(
   "countryName"            => "UK",
   "stateOrProvinceName"    => "Cambridge",
   "localityName"           => "Cambs",
   "organizationName"       => "UniServer",
   "organizationalUnitName" => "Demo",
   "commonName"             => "localhost",
   "emailAddress"           => "me@example.com"
);

Note: Common name for a real signed certificate would be what a user would type into a browser e.g www.fred.com

Top

Function openssl_pkey_new

Function openssl_pkey_new() generates a new private and public key pair.

resource openssl_pkey_new  ([  array $configargs  ] )

Code:

//=== Generate a new private (and public) key pair
$privkey = openssl_pkey_new($config);

Top

Function openssl_csr_new

Function openssl_csr_new() generates a new CSR (Certificate Signing Request) based on the information provided by dn,

mixed openssl_csr_new (array $dn, resource &$privkey [,array $configargs [,array $extraattribs ]] )

Code:

//=== Generate a certificate signing request
$csr = openssl_csr_new($dn, $privkey, $config);

Top

Function openssl_csr_sign

Function openssl_csr_sign() generates an x509 certificate resource from the given CSR.

resource openssl_csr_sign(mixed $csr, mixed $cacert, mixed $priv_key, int $days[,array $configargs[,int $serial = 0 ]])

Code:

//== Create a self-signed certificate valid for 365 days
$sscert = openssl_csr_sign($csr, null, $privkey, 365, $config);

Essentially that completes certificate and key generation! They are currently resources these require extracting to appropriate files. Following function perform this task:

Top

Function openssl_pkey_export_to_file

Function openssl_pkey_export_to_file()saves an ascii PEM encoded verion of key into the file named by outfilename.

bool openssl_pkey_export_to_file(mixed $key,string $outfilename[,string $passphrase[,array $configargs]])

This function is a quick way to kill Apache stone dead! To prevent this ensure you use NULL for $passphrase.

Code:

//== Create key file. Note no passphrase
openssl_pkey_export_to_file($privkey,"server.key",NULL, $config);

Top

Function openssl_x509_export_to_file

Function openssl_x509_export_to_file() exports a certificate to file

bool openssl_x509_export_to_file(mixed $x509, string $outfilename [,bool $notext ])

The optional parameter notext if it is FALSE, additional human-readable information is included in the output.

The default value of notext is TRUE.

Code:

//== Create server certificate 
openssl_x509_export_to_file($sscert,  "server.crt",  FALSE );

Top

Function openssl_csr_export_to_file

Function openssl_csr_export_to_file()exports a CSR to a file

bool openssl_csr_export_to_file(resource $csr, string $outfilename[, bool $notext = true ])

Code:

//== Create a signing request file 
openssl_csr_export_to_file($csr, "server.csr");

Top

Complete code

Edit file gen.php to contain the folloing code:

<?php

//== Determine path
$ssl_path = getcwd();
$ssl_path = preg_replace('/\\\/','/', $ssl_path);  // Replace \ with /

//== Create a configuration array containing path to openssl.cnf 
$config = array(
"config" => "$ssl_path/openssl.cnf"
);

//=== Create data array for certificate information
$dn = array(
   "countryName"            => "UK",
   "stateOrProvinceName"    => "Cambridge",
   "localityName"           => "Cambs",
   "organizationName"       => "UniServer",
   "organizationalUnitName" => "Demo",
   "commonName"             => "localhost",
   "emailAddress"           => "me@example.com"
);

//=== Generate a new private (and public) key pair
$privkey = openssl_pkey_new($config);

//=== Generate a certificate signing request
$csr = openssl_csr_new($dn, $privkey, $config);

//== Create a self-signed certificate valid for 365 days
$sscert = openssl_csr_sign($csr, null, $privkey, 365, $config);

//== Create key file. Note no passphrase
openssl_pkey_export_to_file($privkey,"server.key",NULL, $config);

//== Create server certificate 
openssl_x509_export_to_file($sscert,  "server.crt",  FALSE );

//== Create a signing request file 
openssl_csr_export_to_file($csr, "server.csr");
?>

Run the script, you can manually copy key and certificate to the server.

Top

Summary

The above provides a starting point; with a few minor additions you can automatically copy key and certificate to appropriate server locations. This was introduced to Uniform Server 5.6.7-Nano see folder UniServer\unicon\key_cert_gen

Was it effective in reducing size?

  • Original folder size 1.69 MB
  • New folder size 16.0 KB

With reference to today’s hungry applications not that significant but worth having.

Top

Duplication

When run as a portable application it’s very easy to fall into a trap and proliferate components that are not being picked up. This applies to Uniform Server however there were historical reasons for this that I believe no longer applies.

Taking a step back how are dlls picked-up by Windows? General sequence of events are as follows:

  1. From directory which an application is loaded.
  2. From windows\system32 directory.
  3. From windows\system directory.
  4. From windows directory.
  5. From current directory.
  6. Finally from directories listed in the PATH environment variable.

Note 1 and 5 are similar and leads to confusion. From Apache’s point of view it meets criteria 1. When run as a server it forces PHP to pick-up extra dlls it requires from the Apache folder.

However when PHP is run as CLI it picks-up the extra dlls from its own folder.

It’s a viscous circle that I resolved when using libmysql.dll, same technique is applicable to openssl libraries.

Top

Solution

Make PHP folder prime source of any duplicated files. Apache is amenable to this because it has the following directive:

  • Loadfile

Yep! Probably not what you expected, however it is simple and effective, ideally any additional files should be loaded before any modules.

For example this extract from Nano_5_6_7 httpd.conf

# Example:
# LoadModule foo_module modules/mod_foo.so
#

Loadfile "C:/Nano_5_6_7/UniServer/usr/local/php/ssleay32.dll"
Loadfile "C:/Nano_5_6_7/UniServer/usr/local/php/libeay32.dll"
Loadfile "C:/Nano_5_6_7/UniServer/usr/local/php/libmysql.dll"


LoadModule actions_module modules/mod_actions.so
LoadModule alias_module modules/mod_alias.so

It gives a saving of 1.51 MB

Top

Conclusion

That concludes this short tutorial. You now have a reasonable understanding how to use some of the opensll functions.

Probably what is also import is to avoid duplication in Uniform Server.

I have shown how to save around 3.2M

Top