ovpnCNcheck — an OpenVPN tls-verify script

February 2, 2008

If you’ve running an OpenVPN server you may have asked yourself how you can decide which clients can connect even if they got signed by the same CA. A common case would arises if you provide more than one OpenVPN server but not all clients should be able to connect to every one. Sure it would be possible to use a separate CA for each server but that would not be flexible. The clients would need more than one certificate/key pair and if you want to enable/disable access to a certain server for a client you need to generate/revoke the client certificate. Not a good idea!

I’ve therefore written two scripts with solve this problem. These scripts check if the peer is in the allowed user list by checking the CN (common name) of the X.509 certificate against a provided text file. For example in OpenVPN, you could use the directive:

tls-verify "/usr/local/sbin/ovpnCNcheck.py /etc/openvpn/userlist.txt"

This would cause the connection to be dropped unless the client common name is within the userlist.txt. The bash script will just check if a common name is in one of the lines (one CN per line) and the python version parses the provided regular expressions. Every line should hold one regular expression in this case which can also be just one common name (don’t forget to escape stuff like .?^()[]\ with a \). Empty lines or ones which start with a # are ignored. The bash version works also on a “out of the box” OpenWRT installation.

Python version: ovpncncheck.py
Bash version: ovpncncheck.sh

Hope it helps you!

25 Comments »

RSS feed for comments on this post. TrackBack URI

  1. […] if there is interest I can show some stuff I use to extend this setup. I’ve also written a blog post some time ago which is interesting for you if you use the same CA for different VPN Server and want […]

    Pingback by Howto connect multiple networks over the Internet the cheap way | Robert Penz Blog — October 17, 2010 #

  2. Hi,

    can you give me an example for

    /etc/openvpn/userlist.txt

    Comment by Martin Popov — October 31, 2011 #

  3. An example would be:

    .*\.client\.penz\.name

    Comment by robert — October 31, 2011 #

  4. Hi,

    your script works very well!

    Perhaps you’ve seen the news that the Dutch goverment has a modified version of openvpn (which is still open source and they’ve send a lot of patches upstream): openvpn-NL; which uses polarssl.

    However, polarssl uses another seperating character for the CN: “__” instead of “/”. So in order for your script to work with polarssl the regex needs to be changed so that it matches the __. I have done so for my own installation (re.compile(r”__CN=(?P[^/]+)”).search(x509)). Perhaps you could update your script so that it works with both openssl and polarssl?

    Cheers,

    Jaap

    Comment by Jaap — November 26, 2011 #

  5. How do I write the userlist.txt? What is CN? Is it the arg that I gave to ./build-key script of easy-rsa?

    Comment by shadyabhi — June 21, 2012 #

  6. Hello, thank you for script. Can you give me advise. I´m getting following 51line error. (server is windows machine)

    Wed Dec 12 10:57:30 2012 OpenVPN 2.1.1 i686-pc-mingw32 [SSL] [LZO2] [PKCS11] bui
    lt on Dec 11 2009
    Wed Dec 12 10:57:30 2012 NOTE: when bridging your LAN adapter with the TAP adapt
    er, note that the new bridge adapter will often take on its own IP address that
    is different from what the LAN adapter was previously set to
    Wed Dec 12 10:57:30 2012 NOTE: the current –script-security setting may allow t
    his configuration to call user-defined scripts
    Wed Dec 12 10:57:31 2012 NOTE: –script-security method=’system’ is deprecated d
    ue to the fact that passed parameters will be subject to shell expansion
    Wed Dec 12 10:57:31 2012 Control Channel Authentication: using ‘C:\Program Files
    \OpenVPN\easy-rsa\keys\ta-dns.key’ as a OpenVPN static key file
    Wed Dec 12 10:57:31 2012 TAP-WIN32 device [VPN] opened: \\.\Global\{7BB408CB-0E6
    2-4FCB-9372-76032AF9EF9A}.tap
    Wed Dec 12 10:57:31 2012 NOTE: could not get adapter index for {7BB408CB-0E62-4F
    CB-9372-76032AF9EF9A}
    Wed Dec 12 10:57:31 2012 Sleeping for 10 seconds…
    Wed Dec 12 10:57:41 2012 UDPv4 link local (bound): [undef]:1194
    Wed Dec 12 10:57:41 2012 UDPv4 link remote: [undef]
    Wed Dec 12 10:57:41 2012 Initialization Sequence Completed
    Wed Dec 12 10:58:26 2012 10.239.210.5:1194 Re-using SSL/TLS context
    Wed Dec 12 10:58:26 2012 10.239.210.5:1194 LZO compression initialized
    File “C:\OpenVPN\ovpncncheck.py”, line 51
    print __doc__
    ^
    SyntaxError: invalid syntax
    Wed Dec 12 10:58:26 2012 10.239.210.5:1194 TLS_ERROR: BIO read tls_read_plaintex
    t error: error:140890B2:SSL routines:SSL3_GET_CLIENT_CERTIFICATE:no certificate
    returned
    Wed Dec 12 10:58:26 2012 10.239.210.5:1194 TLS Error: TLS object -> incoming pla
    intext read error
    Wed Dec 12 10:58:26 2012 10.239.210.5:1194 TLS Error: TLS handshake failed

    Comment by Tesar — December 12, 2012 #

  7. The code is for Python 2.x you’re running Python 3.x

    Comment by robert — December 14, 2012 #

  8. It’s OK. THANK YOU.

    Comment by Tesar — December 17, 2012 #

  9. Thanks a lot, worked like a charm.

    Comment by Fernando F — February 11, 2014 #

  10. Hi,

    your bash code do not run. the expr is not right for normal openvpn with x509 certificates.

    now i tried with cut command. here is my example not wonderful but it works:

    echo “ou=bla, CN=blub”|cut -f 6 -d ,|cut -f 2 -d =

    first i select the string with the commas and then i select it by the =

    kind regards

    Comment by max — July 16, 2014 #

  11. thank you

    Comment by دانلود سریال — November 28, 2014 #

  12. Important note! a newline in the userlist.txt file will cause ALL clients to be accepted. If you use the above scripts (both .sh & .py), be sure to not leave any new lines at the end of the file.

    Comment by Nathan — March 21, 2015 #

  13. Great trick. Although I found that in order to get the bash code to work properly, I had to change the regex to
    grep -q “^`expr match “$3” “.*CN=\([^,][^,]*\)”`$” “$1” && exit 0

    Comment by Mathieu — October 8, 2016 #

  14. The post is quite old, its possible that the regex needed to be changed for a newer openvpn. Thx for the update.

    Comment by robert — October 9, 2016 #

  15. All regular expressions here didn’t work for me, so I rebuilt it with the sed command just cutting the parts around the CN away.
    This works for me on OpenWRT 15.05, so I thought I should share it:

    CN=`echo “$3” | sed ‘s/.*CN=//g’ | sed ‘s/, .*$//g’`
    grep -q “$CN” /etc/openvpn/authorized_users_ext && exit 0

    where the CNs of the keys are listed in authorized_users one per line like this:
    user1
    user2

    Comment by simon — January 14, 2017 #

  16. @simon: same for me – thank you for sharing … it looks like the script is not receiving exactly the line from certificate (the one containing slash) but it is called with parameters like these:

    /etc/openvpn/bin/ovpncncheck.py /etc/openvpn/userlist.txt 1 C=XX, ST=XXX, L=XXX, O=XXX XXX, OU=XXX, CN=xxx.xxx, name=server, [email protected]

    using: openvpn 2.3.14

    Comment by peter — March 31, 2017 #

  17. maybe small edit – you are missing ^ and $ in the regex … so it tries to only match whole line or nothing

    CN=`echo “$3” | sed ‘s/.*CN=//g’ | sed ‘s/, .*$//g’`
    grep -q “^$CN$” /etc/openvpn/authorized_users_ext && exit 0

    Comment by peter — March 31, 2017 #

  18. It worked after I edited the RegEx, as tls-verify was sending “, “-separated data in the ‘subject’ parameter, instead of “/”-separated.

    Comment by Jean Chevalier — October 27, 2017 #

  19. More info on the format of X.509 names:

    It looks as if in OpenVPN 2.4 you can still use the option “compat-names” to keep the v.2.3 and prior format, doing away with the need to change the regular expressions in the ovpnCNcheck scripts. The option is due for removal in v.2.5 however.

    Reference (search for text ‘compat-names’):
    https://community.openvpn.net/openvpn/wiki/Openvpn24ManPage

    Comment by Jean Chevalier — October 30, 2017 #

  20. Hi,

    here is my working version of the .sh script. Tested on: Xubuntu 16 x32, OpenVPN 2.3.10 i686-pc-linux-gnu [SSL (OpenSSL)] [LZO] [EPOLL] [PKCS11] [MH] [IPv6] built on Jun 22 2017
    Added the line “log-append /var/log/openvpn.log” to “/etc/openvpn/myserver.conf” to turn on logging. So parameters of “echo” statements from the script were be protocoled too.

    #!/bin/sh

    # ovpnCNcheck — an OpenVPN tls-verify script
    # “””””””””””””””””””””””””””””””””””””””””””
    #
    # This script checks if the peer is in the allowed
    # user list by checking the CN (common name) of the
    # X509 certificate against a provided text file.
    #
    # For example in OpenVPN, you could use the directive
    # (as one line):
    #
    # tls-verify “/usr/local/sbin/ovpnCNcheck.py
    # /etc/openvpn/userlist.txt”
    #
    # This would cause the connection to be dropped unless
    # the client common name is within the userlist.txt.
    #
    # Special care has been taken to ensure that this script
    # also works on openwrt systems where only busybox is
    # available
    #
    # Written by Robert Penz under the GPL 2
    # Parts are copied from the verify-cn sample OpenVPN
    # tls-verify script.

    echo ………. TLS-VERIFY ……….
    #echo $1 $2 $3

    [ $# -eq 3 ] || { echo usage: ovpnCNcheck.sh userfile certificate_depth X509_NAME_oneline ; exit 255 ; }

    # $2 -> certificate_depth
    if [ $2 -eq 1 ] ; then
    # $3 -> X509_NAME_oneline
    # $1 -> cn we are looking for
    grep -q “^`expr match “$3″ ‘.*CN=\([^,]*\)’`$” “$1” && exit 0
    echo “*** W A R N I N G *** User \””`expr match “$3″ ‘.*CN=\([^,]*\)’`”\” not in whitelist (“$1″)!” && exit 1
    fi

    exit 0

    Comment by y0sh1 — December 27, 2017 #

  21. In openvpn 2.4 I used this regex and it seems to be working correctly without using the option “compat-names”

    grep -q “^`expr match “$3” “.*CN=\([^/][^/]*\)”`$” “$1” && exit 0

    Comment by brent — January 31, 2018 #

  22. Nm, that was still with the compat-names option, my bad 🙁

    Comment by brent — January 31, 2018 #

  23. openvpn 2.4 works with what y0sh1 posted

    [ $# -eq 3 ] || { echo usage: ovpnCNcheck.sh userfile certificate_depth X509_NAME_oneline ; exit 255 ; }

    # $2 -> certificate_depth
    if [ $2 -eq 0 ] ; then
    # $3 -> X509_NAME_oneline
    # $1 -> cn we are looking for
    grep -q “^`expr match “$3” “.*CN=\([^,]*\)”`$” “$1” && exit 0
    exit 1
    fi

    exit 0

    Comment by brent — January 31, 2018 #

  24. I run the updated script from Y0sh1, but I kept getting this error:
    I know the script is correct and it’s not a permission error.
    What do you guys suggest I try?

    WARNING: Failed running command (–tls-verify script): external program exited with error status: 1
    VERIFY SCRIPT ERROR: depth=1, CN=cn_xxxxxxxxxxxxxxxxxxx
    OpenSSL: error:1417C086:SSL routines:tls_process_client_certificate:certificate verify failed
    TLS_ERROR: BIO read tls_read_plaintext error
    TLS Error: TLS object -> incoming plaintext read error
    TLS Error: TLS handshake failed
    Fatal TLS error (check_tls_errors_co), restarting
    SIGUSR1[soft,tls-error] received, client-instance restarting

    Comment by Huzar — June 4, 2019 #

  25. for ver2.5 and 1.1.1k:
    `expr match “$3” “.*, CN=\([^,][^,]*\)”`
    has worked.

    Comment by tkomatsu — February 7, 2022 #

Leave a comment

XHTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

Powered by WordPress
Entries and comments feeds. Valid XHTML and CSS. 36 queries. 0.062 seconds.