Set up Certificate Authentication in OpenConnect VPN Server (ocserv)

This tutorial will be showing you how to set up certificate authentication in OpenConnect VPN server (ocserv) on Debian/Ubuntu/CentOS/RHEL. OpenConnect (ocserv) is an open-source implementation of the Cisco AnyConnect VPN protocol.

In a previous article, I explained the steps to set up OpenConnect VPN server with Let’s Encrypt TLS server certificate. Let’s Encrypt does not issue client certificate, so in that article, we used password authentication. Entering username and password every time can be a hassle, especially if the client software, such as the Cisco AnyConnect app on iOS, doesn’t offer an option to remember passwords. Many OpenConnect client software can import user certificates, which will free the user from entering username and password. Certificate authentication is also more secure than password authentication.

Requirements

To follow this tutorial, it’s assumed that you have already set up an OpenConnect VPN server with Let’s Encrypt TLS server certificate. If not, please follow one of the following tutorials.

We will set up our own CA (Certificate Authority) to sign client certificate. The ocserv daemon should continue using the TLS server certificate issued by Let’s Encrypt, so client software won’t display security warning.

Setting up Your Own CA (Certificate Authority)

We want to use certificate authentication, but Let’s Encrypt does not issue client certificate, so we need to create our own CA. You can use openssl to do the job, but ocserv recommends GnuTLS, so I will show you how to use GnuTLS.

Install gnutls-bin package on Debian/Ubuntu server.

sudo apt install gnutls-bin

Install gnutls-utils package on CentOS/RHEL.

sudo dnf install gnutls-utils

Create a sub-directory in /etc/ocserv/ to hold private keys and certificates.

sudo mkdir /etc/ocserv/ssl/

Change your working directory.

cd /etc/ocserv/ssl/

Generate a private key for the CA with the certtool command, which is provided by the gnutls-bin or gnutls-utils package. By default, it generates a 3072 bit RSA key, which is sufficient.

sudo certtool --generate-privkey --outfile ca-privkey.pem

Before generating the CA certificate, let’s create the CA certificate template file. The template file format can be found in certtool manual (man certtool).

sudo nano ca-cert.cfg

Add the following lines to the file. Replace placeholders with the appropriate values.

# X.509 Certificate options

# The organization of the subject.
organization = "vpn.example.com"

# The common name of the certificate owner.
cn = "Example CA"

# The serial number of the certificate.
serial = 001

# In how many days, counting from today, this certificate will expire. Use -1 if there is no expiration date.
expiration_days = -1

# Whether this is a CA certificate or not
ca

# Whether this certificate will be used to sign data
signing_key

# Whether this key will be used to sign other certificates.
cert_signing_key

# Whether this key will be used to sign CRLs.
crl_signing_key

Save and close the file. Now generate the CA certificate using configurations from the template file.

sudo certtool --generate-self-signed --load-privkey ca-privkey.pem --template ca-cert.cfg --outfile ca-cert.pem

Now we have a CA certificate file (ca-cert.pem).

Generating Client Certificate

Now run the following command to generate client private key.

sudo certtool --generate-privkey --outfile client-privkey.pem

Create the client certificate template file.

sudo nano client-cert.cfg

Add the following lines into the file. The uid must be a username in the /etc/ocserv/ocpasswd file.

# X.509 Certificate options
# The organization of the subject.
organization = "vpn.example.com"

# The common name of the certificate owner.
cn = "John Doe"

# A user id of the certificate owner.
uid = "username"

# In how many days, counting from today, this certificate will expire. Use -1 if there is no expiration date.
expiration_days = 3650

# Whether this certificate will be used for a TLS server
tls_www_client

# Whether this certificate will be used to sign data
signing_key

# Whether this certificate will be used to encrypt data (needed
# in TLS RSA ciphersuites). Note that it is preferred to use different
# keys for encryption and signing.
encryption_key

Save and close the file. Then run the following command to generate client certificate, which will be signed by the CA private key.

sudo certtool --generate-certificate --load-privkey client-privkey.pem --load-ca-certificate ca-cert.pem --load-ca-privkey ca-privkey.pem --template client-cert.cfg --outfile client-cert.pem

Combine the client private key and certificate in a PKCS #12 file that is protected by a PIN.

sudo certtool --to-p12 --load-privkey client-privkey.pem --load-certificate client-cert.pem --pkcs-cipher aes-256 --outfile client.p12 --outder

ocserv certificate authentication

Now we have the client private key and certificate combined into one file client.p12.

Note that the Ciso AnyConnect app on iOS doesn’t support AES-256 cipher. It will refuse to import the client certificate. If the user is using iOS device, then you can choose the 3des-pkcs12cipher.

sudo certtool --to-p12 --load-privkey client-privkey.pem --load-certificate client-cert.pem --pkcs-cipher 3des-pkcs12 --outfile ios-client.p12 --outder

The client private key and certificate combined into one file ios-client.p12.

Certificate Signing Request

This step is only needed if there are multiple VPN users, and the user wants to use his/her own private key.

In order to keep end users’ private keys secret, users can generate certificate signing request (CSR) with their own private keys, then send certificate requests to admin, who then issues client certificates to users. First, they have to generate private key and the client certificate template using the commands mentioned above. Then generate a CSR with the following command. The request.pem file is signed by user’s private key.

certtool --generate-request --load-privkey client-privkey.pem --template client-cert.cfg --outfile request.pem

Next, the user sends the request.pem and client-cert.cfg file to admin, who runs the following command to generate client certificate.

sudo certtool --generate-certificate --load-ca-certificate ca-cert.pem --load-ca-privkey ca-privkey.pem --load-request request.pem --template client-cert.cfg --outfile client-cert.pem

After that, the admin sends client-cert.pem certificate file to the user.

Enabling Certificate Authentication in ocserv Daemon

Edit ocserv configuration file.

sudo nano /etc/ocserv/ocserv.conf

In the previous tutorial, we added the following line to enable password authentication.

auth = "plain[passwd=/etc/ocserv/ocpasswd]"

To enable certificate authentication, uncomment the following line.

auth = "certificate"

If the above two lines are both uncommented, that means the user must pass both password authentication and certificate authentication. So if certificate authentication is enough to prove identity, then comment out the first line.

If you allow users to chose either certificate authentication or password authentication, then you should have the following lines instead.

enable-auth = "plain[passwd=/etc/ocserv/ocpasswd]"
auth = "certificate"

Now find the ca-cert parameter. On Debian/Ubuntu, it’s set to

ca-cert = /etc/ssl/certs/ssl-cert-snakeoil.pem

On CentOS 8/RHEL 8, it’s set to

ca-cert = /etc/ocserv/ca.pem

We need to use our own CA certificate to verify client certificate, so change this line to

ca-cert = /etc/ocserv/ssl/ca-cert.pem

Next, find the following line.

cert-user-oid = 0.9.2342.19200300.100.1.1

You don’t need to change it. I just want to tell you that 0.9.2342.19200300.100.1.1 represents the UID filed in client certificate. The above line tells ocserv daemon to find the username from the UID field of client certificate. If the client certificate is successfully verified by the CA certificate and ocserv daemon can find a matching username in /etc/ocserv/ocpasswd file, then the client can login.

Save and close the file. Then restart ocserv.

sudo systemctl restart ocserv

Using Certificate Authentication on Debian/Ubuntu/CentOS/RHEL Desktop

Use the scp command to download the client.p12 file to your Debian/Ubuntu/CentOS/RHEL desktop.

scp [email protected]:/etc/ocserv/ssl/client.p12 ~

Then install the openconnect client software.

Debian/Ubuntu:

sudo apt install openconnect

CentOS/RHEL:

sudo dnf install epel-release
sudo dnf install openconnect

To use certificate authentication, run

sudo openconnect -b vpn.example.com -c client.p12

You will be asked to unlock client private key with the passphrase you set ealier in this tutorial.

ocserv client certificate ubuntu

If the passphrase is entered correctly, you should now be connected to VPN server.

Using Certificate Authentication on Windows and MacOS Desktop

Download OpenConnect GUI client for Window or MacOS from OpenConnect GUI Github Page. Then create a new VPN connection profile and import the PKCS #12 file to user certificate field. Click the Save button. You will need to enter the PIN to unlock the private key. Once imported, you don’t have to enter username and password anymore.

openconnect GUI client certificate authentication

Using Certificate Authentication on iOS Device

iOS users can use the Cisco AnyConnect app. To import client certificate in AnyConnect app, you can first send the PKCS #12 file to your email address in an attachment. Then open the mail app on iOS. Tap the attachment a few seconds and share it with AnyConnect. Then enter the PIN to import the file.

ios anyconenct import client certificate

Once it’s imported, edit your VPN connection in AnyConnect. Go to Advanced -> Certificate and select the client certificate. Save your settings.

ios anyconnect client certificate authentication

Now you don’t have to enter username and password anymore on your iOS device. The Cisco AnyConnect app doesn’t remember username and password, so in password authentication mode, VPN connection will drop when the phone is not in use. In certificate authentication mode, the app will automatically reconnect to VPN server if connection is dropped.

Issues with AnyConnect Client on iOS

The latest version of AnyConnect client on iOS has a problem when using certificate authentication in TLS 1.3 protocol. If you see the following error in the ocserv log (sudo journalctl -eu ocserv), you have this same problem.

GnuTLS error (at worker-vpn.c:795): A TLS fatal alert has been received.

Either you need to use password authentication in the AnyConnect iOS client or disable TLS 1.3 in ocserv configuration file. To disable TLS1.3, find the tls-priorities parameter in the /etc/ocserv/ocserv.conf file, and add :-VERS-TLS1.3 at the end to disable TLS 1.3.

tls-priorities = "NORMAL:%SERVER_PRECEDENCE:%COMPAT:-RSA:-VERS-SSL3.0:-ARCFOUR-128:-VERS-TLS1.0:-VERS-TLS1.1:-VERS-TLS1.3"

Save and close the file. Then restart ocserv.

sudo systemctl restart ocserv

Note: If you see the SSL 3.3 phrase in the ocserv logs, don’t panic. SSL 3.3 is another word for TLS 1.2. You are using a secure TLS connection.

Wrapping Up

I hope this tutorial helped you set up certificate authentication in OpenConnect VPN server. As always, if you found this post useful, then subscribe to our free newsletter to get more tips and tricks. Take care 🙂

Rate this tutorial
[Total: 11 Average: 4.7]

22 Responses to “Set up Certificate Authentication in OpenConnect VPN Server (ocserv)

  • Mysterion
    5 years ago

    Great! thank you!

  • Can I create my own certificate authority like DigiCert for example?

    • Xiao Guo An (Admin)
      5 years ago

      Creating your own certificate authority (CA) has already been explained in this article.

  • Great article!
    is that possible use client certificate + google-authenticator for Two Factor Authentication?
    user is using iOS device

  • @xiao

    Imagine, if one day I want to revoke a certificate which I signed with the CA.
    What should I do?

    Is there any CRL mechanism in place?

  • Sadsbads
    4 years ago

    Dear Team,

    Its a great and very clear guide to use this option. i would like to know about how to revoke the client certificate from OCServ.

  • Trying to get a Chromebook import the p12 certificate. Getting invalid or corrupt file.

  • I’m stuck at PCKS#12 passphrase. I don’t know where I have set my passphrase in this tutorial

  • my bad for above problem. Now I got this error:Failed to read from SSL socket: The transmitted packet is too large(EMSGSIE). Failed to recv DPD request(1434)

  • Eugeniusz
    3 years ago

    Thank you very much for this tutorial – it’s really great! One thing which would be great to cover in this topic is the 2fa for ocserv – for example, with LinOTP. I would appreciate a lot if You could make an article about it 🙂

  • Mike Woodson
    3 years ago

    Thanks for this tutorial!

    @Eugeniusz, you can find 2FA info here: https://gitlab.com/openconnect/recipes/-/blob/master/ocserv-2fa.md

  • you are super awesome! May God bless you!

  • Thanks for the detail article. I have followed along the instructions but I can’t add the certificate to both iPhone and Android device. The prompt for entering passphrase to import certificate keep popping up after I have enter the passphrase, and the certificate is never imported. I can’t figure out the reason for that…

  • 2023-01-28 17:55 -05:00:
    I encountered “password” errors when attempting to import the pkcs12-formatted file, generated as instructed here, into the Cisco AnyConnect app on both iOS and Android mobile devices. The information below describes how I solved this issue.

    This issue occurred using the following server and client mobile devices:

    	Server: Ubuntu 22.04 with certtool 3.7.3
    	Client: Apple iPhone 8 , iOS 16.1.1, AnyConnect 5.0.01241
    	Client: Google Pixel 5a, LineageOS 19 (Android 12), AnyConnect 5.0.00247
    

    Upon attempting to import the pkcs12-formatted file generated per the instructions here, the following error message is returned by the AnyConnect app:
    On iPhone:

    		Error
    		Unable to import certificate due to incorrect password. Do you want to try again?
    

    On Android:

    		Extract certificate
    		Type the correct password.
    		Type the password to extract the  certificates.
    

    Using the following options when generating the pkcs12-formatted file, for BOTH iOS and Android devices, allows the certificate to be successfully imported into AnyConnect on both devices:

    	--pkcs-cipher 3des-pkcs12
    	--hash SHA1
    

    Full command:

    sudo certtool --to-p12 --load-privkey client-privkey.pem --load-certificate client-cert.pem --pkcs-cipher 3des-pkcs12 --hash SHA1 --outfile client.p12 --outder

    The instructions may need to be updated on this page and on other instruction pages on this site, if applicable.

    Help courtesy of:
    Stack Overflow: Installing pcks12 certificate in android “wrong password” bug thread.

    • Carbon
      1 year ago

      Yes! You are right!

    • Thanks, TXCat. Hopefully the guide gets updated to reflect that.

      P.S. Took me a minute to figure out where to import the certificate on the AnyConnect app. You have to tap “Advanced Preferences” then “Certificate” then the “Import” button at the bottom of the screen.

    • Thank you for commenting this solution

  • Hi,

    Firstly, thanks for the article it was very helpful.

    All works well for me when using a Windows client to connect either via username and password, or via the Client certificate authentication.

    However, I have a printer that can accept VPN connections but it requires the output certificate file to be a .pfx rather than the .p12 as created with the commands above. I’ve tried various methods to convert the .p12 to a .pfx but without success, anyone that could help point me in the right direction it would be much appreaciated!

    Thanks in advance

    Adam

  • Just to automate certs little bit. Tested with Ubuntu 22.

    #!/bin/bash

    # Request client_id for file naming
    read -p “Enter client_id: ” client_id

    # Ask if the certificate is for iPhone
    read -p “Is this certificate for an iPhone (yes/no)? ” is_iphone

    # Step 2: Generate client private key
    certtool –generate-privkey –outfile “${client_id}-client-privkey.pem”

    # Step 4: Generate the client certificate
    # Using process substitution to provide the template directly
    certtool –generate-certificate \
    –load-privkey “${client_id}-client-privkey.pem” \
    –load-ca-certificate ca-cert.pem \
    –load-ca-privkey ca-privkey.pem \
    –template <(echo "organization = \"Org_Name\"
    cn = \"${client_id}\"
    uid = \"${client_id}\"
    expiration_days = 3650
    tls_www_client
    signing_key
    encryption_key") \
    –outfile "${client_id}-client-cert.pem"

    # Step 5: Convert the certificate and key to PKCS#12 format
    unix_timestamp=$(date +%s)

    # Choose cipher and hash based on the device
    if [ "$is_iphone" = "yes" ]; then
    pkcs_cipher="3des-pkcs12"
    hash_algo="–hash SHA1"
    else
    pkcs_cipher="aes-256"
    hash_algo=""
    fi

    certtool –to-p12 \
    –load-privkey "${client_id}-client-privkey.pem" \
    –load-certificate "${client_id}-client-cert.pem" \
    –p12-name "${client_id}-${unix_timestamp}" \
    –pkcs-cipher "$pkcs_cipher" \
    $hash_algo \

  • Thank you so much for clear instructions
    but I have a problem for using client certificate on an android device with openconnect app installed ( version 1.33 – Android 13 )
    can you help me with this?

  • I use XCA windows version (latest version: 2.6.0) as client certificates generation tool . It uses OpenSSL 3.1.5 30 Jan 2024. client is openconnect-gui 1.6.0.
    But it does not work with camouflage (http://ocserv.domain/?secretkey).
    ocserv[342399]: worker: client certificate verification succeeded
    Secret not found in URL, declining…

    but It works with client certificates without camouflage and with camouflage and enable-auth = “plain[passwd=/etc/ocserv/ocpasswd]”
    also openconnect-gui works with certtool client certificates.
    Can you explain why openconnect server does’nt work with camouflage and certificates generated by XCA

Leave a Comment

  • Comments with links are moderated by admin before published.
  • Your email address will not be published.
  • Use <pre> ... </pre> HTML tag to quote the output from your terminal/console.
  • Please use the community (https://community.linuxbabe.com) for questions unrelated to this article.
  • I don't have time to answer every question. Making a donation would incentivize me to spend more time answering questions.

The maximum upload file size: 2 MB. You can upload: image. Links to YouTube, Facebook, Twitter and other services inserted in the comment text will be automatically embedded. Drop file here