The following is a recipe on how to create a self-signed X.509 client certificates.
This is one of the steps to implement the FOAF + SSL protocol as outlined by Henry Story.
The starting assumption of this recipe is that your PHP installation has the OpenSSL PHP extension installed and working.
If not the instructions are here: http://uk2.php.net/manual/en/intro.openssl.php
The FOAF+SSL protocol requires that the URI of your FOAF file is stored in the "subjectAltName" within the X509v3 extensions section.
To do this add the following line to the openssl.cnf of your OpenSSL installation
subjectAltName=${ENV::SAN}
This directive should be added to the 'x509_extensions' section.
In our case it was within the [usr_cert] section of /usr/share/ssl/openssl.cnf as specifiec by this directive 'x509_extensions = usr_cert # The extentions to add to the cert
'
Adding as an env variable allows us to set this before the openssl certificates creation calls without editting the openssl.cnf file further.
1, Reference the openssl.cnf file which was editted as outlined in the previous section.
2, Point the certificate creation functions to the x509_extensions directives.
3, Export an env variable 'SAN' which holds the string URI:foaflocation,email:email@address.com. 'SAN' was referenced in the v3 extension directive outlined above.
// Setup the contents of the subjectAltName
if ($foafLocation)
$SAN="URI:$foafLocation";
if ($emailAddress)
{
if ($SAN)
$SAN.=",email:$emailAddress";
else
$SAN="email:$emailAddress";
}
// Export the subjectAltName to be picked up by the openssl.cnf file
if ($SAN)
{
putenv("SAN=$SAN");
}
// Create the array to hold the configuration options for the openssl function calls
// TODO - This should be more easily configured
$config = array('config'=>'/usr/share/ssl/openssl.cnf');
if ($SAN)
{
// TODO - This should be more easily configured
$config = array_merge($config, array('x509_extensions' => 'usr_cert'));
}
NOTE: The private/public key pair is only held in memory at this stage.
// Generate a new private (and public) key pair
$privkey = openssl_pkey_new($config);
if ($privkey==FALSE)
{
while (($e = openssl_error_string()) !== false)
{
echo $e . "\n";
print "<br><br>";
}
}
NOTE: The signing request is only held in memory.
$dn = array(
"countryName" => "UK",
"stateOrProvinceName" => "Somerset",
"localityName" => "Glastonbury",
"organizationName" => "The Brain Room Limited",
"organizationalUnitName" => "PHP Documentation Team",
"commonName" => "Wez Furlong",
"emailAddress" => "wez@example.com"
);
// Generate a certificate signing request
$csr = openssl_csr_new($dn, $privkey, $config);
if (!$csr)
{
while (($e = openssl_error_string()) !== false)
{
echo $e . "\n";
print "<br><br>";
}
}
NOTE: The certificate is only held in memory.
// You will usually want to create a self-signed certificate at this
// point until your CA fulfills your request.
// This creates a self-signed cert that is valid for 365 days
$sscert = openssl_csr_sign($csr, null, $privkey, 365, $config);
if ($sscert==FALSE)
{
while (($e = openssl_error_string()) !== false)
{
echo $e . "\n";
print "<br><br>";
}
}
NOTE: The PKCS12 certificate is only held in memory.
if (openssl_pkcs12_export($sscert, $p12Out, $privkey, $p12Password)==FALSE)
{
// Show any errors that occurred here
while (($e = openssl_error_string()) !== false)
{
echo $e . "\n";
print "<br><br>";
}
}
Putting it all in a single function.
// Returns a p12 encoded SSL certificate
function create_identity_p12(
$countryName, $stateOrProvinceName, $localityName, $organizationName, $organizationalUnitName, $commonName, $emailAddress,
$foafLocation, $p12Password)
{
// Create the DN array for the openssl function calls
if ($countryName)
$dn = array("countryName" => $countryName);
if ($stateOrProvinceName)
{
if ($dn)
$dn = array_merge($dn, array("stateOrProvinceName" => $stateOrProvinceName));
else
$dn = array("stateOrProvinceName" => $stateOrProvinceName);
}
if ($localityName)
{
if ($dn)
$dn = array_merge($dn, array("localityName" => $localityName));
else
$dn = array("localityName" => $localityName);
}
if ($organizationName)
{
if ($dn)
$dn = array_merge($dn, array("organizationName" => $organizationName));
else
$dn = array("organizationName" => $organizationName);
}
if ($organizationalUnitName)
{
if ($dn)
$dn = array_merge($dn, array("organizationalUnitName" => $organizationalUnitName));
else
$dn = array("organizationalUnitName" => $organizationalUnitName);
}
if ($commonName)
{
if ($dn)
$dn = array_merge($dn, array("commonName" => $commonName));
else
$dn = array("commonName" => $commonName);
}
if ($emailAddress)
{
if ($dn)
$dn = array_merge($dn, array("emailAddress" => $emailAddress));
else
$dn = array("emailAddress" => $emailAddress);
}
// if the $dn array is NULL at this point set country name to the default of GB
if (!$dn)
$dn = array("countryName" => "GB");
// Setup the contents of the subjectAltName
if ($foafLocation)
$SAN="URI:$foafLocation";
if ($emailAddress)
{
if ($SAN)
$SAN.=",email:$emailAddress";
else
$SAN="email:$emailAddress";
}
// Export the subjectAltName to be picked up by the openssl.cnf file
if ($SAN)
{
putenv("SAN=$SAN");
}
// Create the array to hold the configuration options for the openssl function calls
// TODO - This should be more easily configured
$config = array('config'=>'/usr/share/ssl/openssl.cnf');
if ($SAN)
{
// TODO - This should be more easily configured
$config = array_merge($config, array('x509_extensions' => 'usr_cert'));
}
// Generate a new private (and public) key pair
$privkey = openssl_pkey_new($config);
if ($privkey==FALSE)
{
// Show any errors that occurred here
while (($e = openssl_error_string()) !== false)
{
echo $e . "\n";
print "<br><br>";
}
}
// Generate a certificate signing request
$csr = openssl_csr_new($dn, $privkey, $config);
if (!$csr)
{
// Show any errors that occurred here
while (($e = openssl_error_string()) !== false)
{
echo $e . "\n";
print "<br><br>";
}
}
// You will usually want to create a self-signed certificate at this
// point until your CA fulfills your request.
// This creates a self-signed cert that is valid for 365 days
$sscert = openssl_csr_sign($csr, null, $privkey, 365, $config);
if ($sscert==FALSE)
{
// Show any errors that occurred here
while (($e = openssl_error_string()) !== false)
{
echo $e . "\n";
print "<br><br>";
}
}
if (openssl_pkcs12_export($sscert, $p12Out, $privkey, $p12Password)==FALSE)
{
// Show any errors that occurred here
while (($e = openssl_error_string()) !== false)
{
echo $e . "\n";
print "<br><br>";
}
}
return $p12Out;
}
// Send the p12 encoded SSL certificate as a file transfer
function download_identity_p12($p12, $foafLocation)
{
// set headers
header("Pragma: private");
header("Expires: 0");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header("Cache-Control: private");
header("Content-Description: File Transfer");
header("Content-Type: application/x-pkcs12");
$file = basename($foafLocation);
header("Content-Disposition: attachment; filename=\"$file.p12\"");
header("Content-Transfer-Encoding: binary");
header("Content-Length: " . strlen($p12));
print($p12);
flush();
if (connection_status()!=0)
{
@fclose($file);
die();
}
}
// Check if the foaf loaction is specified in the script call
$foafLocation = $_GET[foaf];
if (!$foafLocation)
{
if (array_key_exists('foaf', $_GET))
$query = $_SERVER[QUERY_STRING];
else
$query = ($_SERVER[QUERY_STRING]?$_SERVER[QUERY_STRING]."&":"") . "foaf=";
print "Please specify the location of your foaf file. <a href='https://foaf.me/cert.php?" . $query .
"'>https://foaf.me/cert.php?foaf=</a><font color='red'><b>http://foaf.me/nickname</b></font><br><br>
The FOAF location is added to the SubjectAltName within the SSL Client Certificate<br>";
exit();
}
// Check that script is called using the HTTPS protocol
if ($_SERVER[HTTPS] == NULL)
{
print "Please use the following secure uri to download the Identity P12. <a href='https://foaf.me/cert.php?" . $_SERVER[QUERY_STRING] .
"'>https://foaf.me/cert.php?" . $_SERVER[QUERY_STRING] . "</a><br>";
exit();
}
// Get the rest of the script parameters
$countryName = $_GET[countryName];
$stateOrProvinceName = $_GET[stateOrProvinceName];
$localityName = $_GET[localityName];
$organizationName = $_GET[organizationName];
$organizationalUnitName = $_GET[organizationalUnitName];
$commonName = $_GET[commonName];
$emailAddress = $_GET[emailAddress];
$p12Password = $_GET[password];
// Create a p12 encoded SSL certificate
if ( $p12 = create_identity_p12(
$countryName, $stateOrProvinceName, $localityName, $organizationName, $organizationalUnitName, $commonName, $emailAddress,
$foafLocation, $p12Password ) )
{
// Send the p12 encoded SSL certificate to the script caller as a file transfer
download_identity_p12($p12, $foafLocation);
}
The server script has access to the private/public key pair. A malicious script could farm this information for later use.