#!/usr/local/bin/php -f
<?php
/**************************************************************************
*    TLS CN and OCSP Checking Script for OpenVPN version 0.2
*
*    (c) 2013, Information Networks, Ltd. 
*    All Rights Reserved.
*
*    URL: http://www.hsdn.org 
*    Email: info@hsdn.org
***************************************************************************

Adds to rc.conf:
    openvpn_enable="YES"
    openvpn_flags="--script-security 2"

Adds to openssl.conf:
    tls-verify "/usr/local/sbin/tls-verify.php"

Example userlist users.txt:
    user1
    user2
    host.domain.com
*/

// Userlist file (splitted by "\n")
$userlist '/usr/local/etc/openvpn/users.txt';

// OCSP URL address
$ocspuri 'http://ocsp.ca.hsdn.org/tls/';

// CA cert path
$capath '/usr/local/etc/openvpn/keys/ca-bundle.pem';

// Issuer cert path
$issuerpath '/usr/local/etc/openvpn/keys/tls-ca-cert.crt';

// --------------------------------------------------------------------

if (sizeof($argv) < 2)
{
    
returns(1'Missing arguments.');
}

if (
intval($argv[1]) == 0)
{
    
$cert_serial $_ENV['tls_serial_0'];
    
$cert_subject $argv[2];

    if (!
ocsp_check())
    {
        
returns(1'OCSP check error.');
    }

    if (!
cn_check())
    {
        
returns(1'User check error: '.$cert_subject);
    }
}

returns(0'OK');


/**
 * Exit and write log
 *
 * @param    string
 * @return    array
 */
function returns($state$log)
{
    
openlog('openvpn'LOG_ODELAYLOG_AUTH);
    
syslog(LOG_WARNING'tls-verify: '.$log."\n");
    
closelog();

    exit(
$state);
}

/**
 * Check OCSP
 *
 * @return    bool
 */
function ocsp_check()
{
    global 
$ocspuri$capath$issuerpath$cert_serial;

    
$cert_serial strtoupper(sprintf("%1$02s"base_convert($cert_serial1016)));

    
$status shell_exec
    
(
        
'openssl ocsp -nonce -timeout 2'.
        
' -CAfile '.$capath.
        
' -issuer '.$issuerpath.
        
' -url '.$ocspuri.
        
' -serial 0x'.$cert_serial.' 2>/dev/null'
    
);

    if (
$status AND preg_match("/0x".$cert_serial.": ([^\n]+)/i"$status$parse))
    {
        if (
$parse[1] != 'good')
        {
            return 
FALSE;
        }
    }

    return 
TRUE;
}

/**
 * Check user
 *
 * @return    bool
 */
function cn_check()
{
    if (!
$users read_users() OR !$subject read_subject())
    {
        return 
FALSE;
    }

    foreach (
$subject as $pos)
    {
        if (
$pos['name'] == 'CN' AND in_array($pos['value'], $users))
        {
            return 
TRUE;
        }
    }

    return 
FALSE;
}

/**
 * Parse certificate subject
 *
 * @return    array
 */
function read_subject()
{
    global 
$cert_subject;

    
$return = array();

    
$delim "#(,\s|\/)([a-z0-9.]+)\=#si";
    
$subject ', '.trim($cert_subject);

    if (!
preg_match_all($delim$subject$namesPREG_PATTERN_ORDER))
    {
        return 
FALSE;
    }

    
$values preg_split($delim$subject);

    foreach (
$names[2] as $index => $name)
    {
        
$index++;

        if (!isset(
$values[$index]))
        {
            continue;
        }

        
$return[] = array
        (
            
'name' => $name,
            
'value' => trim($values[$index], ', '),
        );
    }

    return 
$return;
}

/**
 * Read and parse user list
 *
 * @return    array
 */
function read_users()
{
    global 
$userlist;

    if (
$list = @file($userlist))
    {
        return 
array_map('trim'$list);
    }

    return 
FALSE;
}

/* End of file */