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!


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


    Comment by Martin Popov — October 31, 2011 #

  3. An example would be:


    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?



    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
    Wed Dec 12 10:57:31 2012 NOTE: could not get adapter index for {7BB408CB-0E62-4F
    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 Re-using SSL/TLS context
    Wed Dec 12 10:58:26 2012 LZO compression initialized
    File “C:\OpenVPN\ovpncncheck.py”, line 51
    print __doc__
    SyntaxError: invalid syntax
    Wed Dec 12 10:58:26 2012 TLS_ERROR: BIO read tls_read_plaintex
    t error: error:140890B2:SSL routines:SSL3_GET_CLIENT_CERTIFICATE:no certificate
    Wed Dec 12 10:58:26 2012 TLS Error: TLS object -> incoming pla
    intext read error
    Wed Dec 12 10:58:26 2012 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:

    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’):

    Comment by Jean Chevalier — October 30, 2017 #

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. 69 queries. 0.101 seconds.