Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

openssl_pkey_new() throwing errors -- Proper openssl.cnf setup for php

**Okay, It's become clear that this issue is an issue related with the setup of openssl on the Linux server and how to properly setup a custom openssl.cnf file. I am not looking for anything complicated but I need a front-end to be able to create self-signed client certificates for authentication to my webservice. So I need to be able to use my CA to create intermediate CAs for client companies and then allow them a secure interface to issue client certificates for their employees. Logins are based on whether you belong to a specific intermediate CA and that your certificate or the intermediate CA hasn't be revoked.

For anyone wondering, we can use self-signed certificates because they are only used for our server to authenticate users and since we issued them, we trust them. Also it would be way too expensive for a startup to establish themselves as an intermediate CA through the commercial offerings AFAIK. Microsoft can do that, we can't. Our webserver itself uses a CA signed certificate.

I know that php code for setting this kind of thing up is straight forward but what isn't is how to properly setup openssl. I have tried several different examples on the net and none of them seem to work for my setup and they all seem to be different. One box was fresh install of Centos 6.2 and I am still getting errors.

Can anyone point me in the proper direction for setting up openssl, apache2 and php so that I can use these php libraries without errors? Our virtual server is using debian squeeze and I have full control of software installed.

Thanks.

open_pkey_new() is returning errors such as error:0E06D06C:configuration file routines:NCONF_get_string:no value. Yet I am passing a path to a openssl.cnf file so I don't know why I'm still getting this problem. Here's my relevent code

<?php
$cwd=getcwd();
$distname= array(
    "countryName" => "CA",
    "stateOrProvinceName" => "Ontario",
    "localityName" => "Toronto",
    "organizationName" => "G4 Apps",
    "organizationalUnitName" => "Development",
    "commonName" => "Mark Lane",
    "emailAddress" => "nobody at gmail.com"
        );
$password = 'seanix';

$cacert_location=$cwd."/certs/CA/g4CA.crt";
$cakey_location=$cwd."/certs/CA/g4CA.key";
$cnf=$cwd.'/certs/myopenssl.cnf';
$configArgs = array(
        'config' =>$cnf
);
?>

Here's my function that makes the keys.

<?php
function makekey($password,$configArgs) {
    $key= openssl_pkey_new($configArgs);
    //print_r($configArgs);
    openssl_pkey_export($key, $pkeyout,$password);
    if (($e=openssl_error_string()) ==false) return $pkeyout;
    else {
        do {

            echo $e . "<BR>";
        } while($e=openssl_error_string());
        return -1;
    }
}
?>

I've tried relative paths too to the configfile and it still won't work. Looks like it might be the host providers ssl setup. I switched to a local virtual machine and I got the key to generate but now I'm getting the same error when creating a csr.

error:0E06D06C:configuration file routines:NCONF_get_string:no value

<?php
function newcsr($distname,$key,$configArgs) {
    $csr=openssl_csr_new($distname,$key,$configArgs);
    openssl_csr_export($csr, $csrout);
    if (($e=openssl_error_string()) ==false) return $csrout;
    else {
        do {

            echo $e . "<BR>";
        } while($e=openssl_error_string());
        return -1;
    }
}
?>

openssl.conf This looks to be an error in openssl.cnf so I've included the file.

HOME            = .
RANDFILE        = $ENV::HOME/.rnd

oid_section     = new_oids


[ new_oids ]


tsa_policy1 = 1.2.3.4.1
tsa_policy2 = 1.2.3.4.5.6
tsa_policy3 = 1.2.3.4.5.7

####################################################################
[ ca ]
default_ca  = g4CA      

####################################################################
[ g4CA ]

dir     = /home/g4apps/secure.g4apps.com/generator/certs    
certs       = $dir/     
crl_dir     = $dir/crl      
database    = $dir/index.txt    


new_certs_dir   = $dir/newcerts     

certificate = $dir/CA/g4CA.crt  
serial      = $dir/serial       
crlnumber   = $dir/crlnumber    

crl     = $dir/CA/g4CA.crl  
private_key = $dir/CA/g4CA.key  
RANDFILE    = $dir/private/.rand    

x509_extensions = usr_cert      

name_opt    = ca_default        
cert_opt    = ca_default        


default_days    = 365           # how long to certify for
default_crl_days= 30            # how long before next CRL
default_md  = default       # use public key default MD
preserve    = no            # keep passed DN ordering

policy      = policy_match


[ policy_match ]
countryName     = match
stateOrProvinceName = match
organizationName    = match
organizationalUnitName  = optional
commonName      = supplied
emailAddress        = optional

[ policy_anything ]
countryName     = optional
stateOrProvinceName = optional
localityName        = optional
organizationName    = optional
organizationalUnitName  = optional
commonName      = supplied
emailAddress        = optional

####################################################################
[ req ]
default_bits        = 2048
default_md      = md5
default_keyfile     = privkey.pem
distinguished_name  = req_distinguished_name
attributes      = req_attributes
x509_extensions = v3_ca # The extentions to add to the self signed cert

string_mask = utf8only

[ req_distinguished_name ]
countryName         = Country Name (2 letter code)
countryName_default     = CA
countryName_min         = 2
countryName_max         = 2

stateOrProvinceName     = State or Province Name (full name)
stateOrProvinceName_default = ON

localityName            = Locality Name (eg, city)
localityName_default    = Toronto

0.organizationName      = Organization Name (eg, company)
0.organizationName_default  = G4 Apps



organizationalUnitName      = Organizational Unit Name (eg, section)

commonName          = Common Name (eg, your name or your server\'s hostname)
commonName_max          = 64

emailAddress            = Email Address
emailAddress_default        = [email protected]
emailAddress_max        = 64


[ req_attributes ]
challengePassword       = A challenge password
challengePassword_min       = 4
challengePassword_max       = 20

unstructuredName        = An optional company name

[ usr_cert ]

nsComment           = "OpenSSL Generated Certificate"

subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid,issuer



[ v3_req ]


basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment

[ v3_ca ]


subjectKeyIdentifier=hash

authorityKeyIdentifier=keyid:always,issuer
basicConstraints = CA:true


[ crl_ext ]

authorityKeyIdentifier=keyid:always

[ proxy_cert_ext ]
basicConstraints=CA:FALSE

nsComment           = "OpenSSL Generated Certificate"

subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid,issuer

proxyCertInfo=critical,language:id-ppl-anyLanguage,pathlen:3,policy:foo

####################################################################
[ tsa ]

default_tsa = tsa_config1   

[ tsa_config1 ]

dir             = ./demoCA      
serial      = $dir/tsaserial    
crypto_device   = builtin       
signer_cert = $dir/tsacert.pem  

certs       = $dir/cacert.pem   
signer_key  = $dir/private/tsakey.pem 
default_policy  = tsa_policy1       
other_policies  = tsa_policy2, tsa_policy3
digests     = md5, sha1     
accuracy    = secs:1, millisecs:500, microsecs:100  
clock_precision_digits  = 0 
ordering        = yes   

tsa_name        = yes   
ess_cert_id_chain   = no    

Stack trace strace php getkeystore.php &> stack.trace

http://secure.g4apps.com/generator/stack.trace

like image 418
Codeguy007 Avatar asked Aug 21 '12 18:08

Codeguy007


2 Answers

I've hit this question a dozen times, so time to put in my 2 cents: An otherwise valid/super complex openssl.cnf can cause up to 10 warnings due to (IMHO) some backwards config parsing done by PHP. After banging my head on the wall I made a shim so that OpenSSL and PHP can coexist peacefully.

Instead of messing with your openssl.cnf, make your own skeleton cnf and include the default in it, like so:

#PHP shim for an otherwise beautiful openssl.cnf
#Notes:
# duplicate OID definitions fail
# duplicate OID usage generates a warning in most cases
# All duplicate sections/values are overlayed: PHP > shim > include > default
RANDFILE    = /dev/null #PHP warns if this doesn't exist
oid_file    = /dev/null #PHP warns if this doesn't exist
#PHP warns if oid_section isn't in the default section
#PHP warns if oid_section is used in another section (only on initialization)
oid_section = php_oids  #set an empty OID section
.include /etc/ssl/openssl.cnf    #include our working conf
[ req ]
  #included format differs from expected format
  attributes         = php_attr #openssl_csr_new()
  #not set in include
  encrypt_rsa_key    = yes #overriden by encrypt_key
  #uncomment to override include, or if otherwise unset
  #req_extensions     = php_req_extension #overridden by req_extensions
  #x509_extensions    = php_x509_extension #overridden by x509_extensions
  #default_bits       = 4096          #overridden by private_key_bits
  #default_md         = sha512        #overridden by digest_alg
  #string_mask        = utf8only      #overridden by string_mask
  #distinguished_name = php_distinguished_name #openssl_csr_new()
[ php_attr ] #empty attributes section
  #challengePassword = password
  #unstructuredName = i_prefer_structure
  ##NO *_min,*_max,*_default
  ##challengePassword         = A challenge password (6-20 characters)
  ##challengePassword_min     = 6
  ##challengePassword_max     = 20
  ##challengePassword_default = this_wont_work
[ php_oids ] #empty OID section (no duplicates in this section)
  #test_cert = 2.23.140.2.1
  ##NO short_id=long_id,id_num
  ##TEST = test_cert, 2.23.140.2.1
[ php_distinguished_name ] #empty DN section
  #commonName         = Common Name (CN)
  #commonName_min     = 1
  #commonName_max     = 63
  #commonName_default = this_works
  #streetAddress      = this_also_works
  #0.organizationalUnitName = this_actually_works
  #ONLY THE FIRST OID IS USED
  ##1.organizationalUnitName = this_is_silently_discarded
[ php_x509_extension ] #empty x509 extension section
  subjectKeyIdentifier   = hash #at least one value required
  #authorityKeyIdentifier = keyid:always
  #keyUsage               = critical, digitalSignature, cRLSign, keyCertSign
  #basicConstraints       = critical, CA:true, pathlen:0
  #certificatePolicies    = ia5org, test_cert
  #authorityInfoAccess    = @ocsp_ext
  #crlDistributionPoints  = @crl_ext
  #tlsfeature             = status_request_v2
[ php_req_extension ] #empty req extension section
  subjectKeyIdentifier   = hash #at least one value required
  #keyUsage               = critical, nonRepudiation, digitalSignature, keyEncipherment
  #extendedKeyUsage       = critical, clientAuth, emailProtection
  #basicConstraints       = critical, CA:FALSE
  #certificatePolicies    = ia5org, test_cert
  #authorityInfoAccess    = @ocsp_ext
  #crlDistributionPoints  = crl_ext
  #tlsfeature             = status_request_v2
  #nsComment              = "OpenSSL 1.1.1c Generated Client Certificate"

Other than the [req] section, feel free to remove all the comments to minify the file.

Here's corresponding PHP file to test this out:

<?php
  //Serial can't be our desired 20 byte random hex:
  //  [bin2hex(random_bytes(20))] is ideal
  //  [8 bytes] PHP_INT_MAX
  //  file a bug report?
  //NO subjectAltName !!!!!
  //ini_set('openssl.cafile','/etc/ssl/certs/my-ca.crt');
  //ini_set('openssl.capath','/etc/ssl/certs/');
  $pass='password';
  $capass='capass';
  /* it's best to set all of these values in PHP to avoid confusion */
  $config=[
   /*'digest_alg'      =>'sha512',                     /*default_md*/ /*openssl_get_md_methods()*/
   /*'private_key_bits'=>8192,                         /*default_bits*/
   /*'encrypt_key'     =>true,                         /*encrypt_key,encrypt_rsa_key*/
   /*'string_mask'     =>'utf8only',                   /*string_mask - undocumented*/
   'x509_extensions' =>'x509_ext_orig',                /*x509_extensions*/
   'req_extensions'  =>'usr_cert_orig',                /*req_extensions*/
   'private_key_type'  =>OPENSSL_KEYTYPE_EC,
   'encrypt_key_cipher'=>OPENSSL_CIPHER_AES_256_CBC,
   'curve_name'        =>'secp384r1',                /*openssl_get_curve_names()*/
   'config'            =>'php-openssl.cnf'           /* export OPENSSL_CONF=php-openssl.cnf */
  ];
  /* all values here OVERWRITE any default DN value */
  $dn=[
   /*'name'=>'', //FAILS- NO EMPTY VALUES*/
   'OU'=>'override_original_OU',
   'surname'=>'new_surname'
  ];
  /* all values here ADD to the default. this array can be multi-dimensional */
  $csrargs=[
   'surname'=>'additional_surname',
   'OU'=>['second_OU','third_OU']
  ];
  $pkargs=[
   /*'extracerts'=>'',*/
   'friendly_name'=>'php-cert'
  ];
  while($err=openssl_error_string()) echo("openssl_init- {$err}\n");
  if($pkey=openssl_pkey_new($config)){ /* create a new private key */
    while($err=openssl_error_string()) echo("openssl_pkey_new- {$err}\n");
    $csr=openssl_csr_new($dn,$pkey,$config,$csrargs); /* generate a csr */
    while($err=openssl_error_string()) echo("openssl_csr_new- {$err}\n");
    print_r(openssl_csr_get_subject($csr,true)); /* show the dn */
    /* sign our CSR using the largest random serial we can */
    $x509=openssl_csr_sign($csr,'file:///etc/ssl/certs/int-ca.crt',['file:///etc/ssl/private/int-ca.key',$capass],30,$config,random_int(72057594037927936,PHP_INT_MAX));
    while($err=openssl_error_string()) echo("openssl_csr_sign- {$err}\n");
    if($x509!==false){
      openssl_pkcs12_export_to_file($x509 ,'/tmp/phpcert.pfx',$pkey,$pass,$pkargs); /* export the keypair as pfx */
      while($err=openssl_error_string()) echo("openssl_pkcs12_export_to_file- {$err}\n");
      openssl_pkey_export_to_file($pkey,'/tmp/phpcert.key',$pass,$config); /* export the private key */
      while($err=openssl_error_string()) echo("openssl_pkey_export_to_file- {$err}\n");
      openssl_pkey_free($pkey); /* free memory */
      openssl_x509_export_to_file($x509,'/tmp/phpcert.crt',true); /* export the signed certificate */
      openssl_x509_free($x509); /* free memory */
      while($err=openssl_error_string()) echo("openssl_x509_export_to_file- {$err}\n");
  }else{
    while($err=openssl_error_string()) echo("openssl_pkey_new- {$err}\n");
  }
?>

Hopefully this helps.

like image 86
Joseph Riopelle Avatar answered Oct 23 '22 04:10

Joseph Riopelle


When using openssl_csr_new make sure the first parameter $dn does not contain keys with empty values.

For example, this call to openssl_csr_new would trigger the error

0E06D06C:configuration file routines:NCONF_get_string:no value

<?php

$dn = [
    'CN' => 'example.com',
    'ST' => '',
    'C'  => '',
    'O'  => '',
];

openssl_csr_new($dn, $privKey);
like image 33
daiwai Avatar answered Oct 23 '22 06:10

daiwai