Skip to content

OpenVPN Client Export Bug #756

Closed

Description

@cvanbrummelen

Describe the bug
When using the Export endpoint it returns "The OpenVPN client export failed for the following reason: Could not find client certificate.".
However the certificate does exist but the code seems to be using the wrong ID.

From my other post:

I did some further debugging and it seems the code from openvpn-client-export.inc (from pfSense itself) is returning the wrong thing.
In this line "$cert = $user['cert'][$crtid];" the $crtid is 30. However the $user array/object returning has the cert with id 30. The certificate itself is ID 30 but inside the $user object/array it has a key value of 0.
When I hardcore the $crtid to 0 it does return the configs.

To Reproduce
Below my example function I do for the call

function testFunction()
{
    global $apibaseurl, $apikey;
    // CREATE USER CERTIFICATE (pfSense API v1)
    $endpoint = $apibaseurl. 'vpn/openvpn/client_export';

    $payload = array(
            "id" => "0",  // OpenVPN server ID
            "type" => "confzip",
            "certref" => "XXXfa75f76XXX",  // VERIFY THIS EXACT ID FROM listCerts()
            "username" => "XXX_WarXXetxx",
    );
    
    $json = json_encode($payload);
    
    $ch = curl_init($endpoint);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_POST, 1);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $json);
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
    curl_setopt($ch, CURLOPT_HTTPHEADER, array(
            "Content-Type: application/json",
            'Accept: application/octet-stream',  // Critical for binary ZIP response
            'x-api-key: ' . $apikey
    ));
    $response = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    if (curl_error($ch)) {
        echo "cURL Error: " . curl_error($ch);
    }
    curl_close($ch);
    
    if ($httpCode == 200 && $response !== false) {
        // Binary ZIP data - save to file instead of json_decode
        header('Content-Type: application/octet-stream');
        header('Content-Disposition: attachment; filename="openvpn_XXX_WarXXetxx.zip"');
        header('Content-Length: ' . strlen($response));
        echo $response;
        //echo "Export successful! ZIP saved as 'openvpn_export.zip' (size: " . strlen($response) . " bytes)\n";
    } else {
        $data = json_decode($response, true);
        $data['http_code'] = $httpCode;
        echo "<pre>Error Response:\n";
        print_r($data);
        echo "</pre>";
    }
}

Expected behavior
I expect to get the client export as a ZIP. However I get the error. But when I change the line in openvpn-client-export.inc it does give me back the correct certificate. So the Payload I use in my code does seem to be correct I think.

pfSense Version & Package Version:

  • pfSense Version: 2.8.1-RELEASE
  • Package Version v2.6.0

Affected Endpoints:

  • URL: /api/v2/vpn/openvpn/client_export

If there is more information needed please let me know!

Activity

  1. added
    troubleshootingIssues that are troubleshooting requests or need troubleshooting before proceeding
    on Sep 24, 2025
  2. jaredhendrickson13 commented on Sep 27, 2025

    @jaredhendrickson13
    Member

    Having trouble reproducing this. Here's what I'm trying on a fresh install of CE 2.8.1:

    1. Install pfSense-pkg-openvpn-client-export
    curl -s -k -u admin:pfsense -X POST https://pfsense-2.8.1-release.jaredhendrickson.com/api/v2/system/package -H "content-type: application/json" -d '{"name": "pfSense-pkg-openvpn-client-export"}'
    
    {
      "code": 200,
      "status": "ok",
      "response_id": "SUCCESS",
      "message": "",
      "data": {
        "id": 0,
        "name": "pfSense-pkg-openvpn-client-export",
        "shortname": "openvpn-client-export",
        "descr": "Exports pre-configured OpenVPN Client configurations directly from pfSense\nsoftware.",
        "installed_version": "1.9.5",
        "latest_version": "1.9.5",
        "update_available": false
      }
    }
    1. Generate a new CA for the VPN
    curl -k -u admin:pfsense -X POST https://pfsense-2.8.1-release.jaredhendrickson.com/api/v2/system/certificate_authority/generate -H "content-type: application/json" -d '{"descr": "vpn_ca", "keytype": "RSA", "keylen": 2048, "digest_alg": "sha256", "trust": true, "randomserial": true, "lifetime": 3650, "is_intermediate": false, "dn_country": "US", "dn_state": "Utah", "dn_city": "Salt Lake City", "dn_organization": "ACME Org", "dn_organizationalunit": "IT"}'
    
    {
      "code": 200,
      "status": "ok",
      "response_id": "SUCCESS",
      "message": "",
      "data": {
        "id": 0,
        "descr": "vpn_ca",
        "refid": "68d851be0a7ff",
        "trust": true,
        "randomserial": true,
        "serial": 1,
        "is_intermediate": false,
        "caref": null,
        "keytype": "RSA",
        "keylen": 2048,
        "ecname": null,
        "digest_alg": "sha256",
        "lifetime": 3650,
        "dn_commonname": "internal-ca",
        "dn_country": "US",
        "dn_state": "Utah",
        "dn_city": "Salt Lake City",
        "dn_organization": "ACME Org",
        "dn_organizationalunit": "IT",
        "crt": "-----BEGIN CERTIFICATE-----\nMIIEODCCAyCgAwIBAgII...UUujXjIlDW10QvnB0fXY6xVg5Tr9m6Q==\n-----END CERTIFICATE-----\n"
      }
    }
    1. Create a server certificate for the VPN
    curl -k -u admin:pfsense -X POST https://pfsense-2.8.1-release.jaredhendrickson.com/api/v2/system/certificate/generate -H "content-type: application/json" -d '{"descr": "vpn_server_cert", "keytype": "RSA", "keylen": 2048, "digest_alg": "sha256", "lifetime": 365, "type": "server", "dn_commonname": "vpn_server", "caref": "68d851be0a7ff"}'
    
    {
      "code": 200,
      "status": "ok",
      "response_id": "SUCCESS",
      "message": "",
      "data": {
        "id": 1,
        "descr": "vpn_server_cert",
        "refid": "68d853e619706",
        "caref": "68d851be0a7ff",
        "keytype": "RSA",
        "keylen": 2048,
        "ecname": null,
        "digest_alg": "sha256",
        "lifetime": 365,
        "dn_commonname": "vpn_server",
        "dn_country": null,
        "dn_state": null,
        "dn_city": null,
        "dn_organization": null,
        "dn_organizationalunit": null,
        "type": "server",
        "dn_dns_sans": [],
        "dn_email_sans": [],
        "dn_ip_sans": [],
        "dn_uri_sans": [],
        "crt": "-----BEGIN CERTIFICATE-----\nMIIEWjCCA0KgAwIBAgII...MNcvFaeoGAI=\n-----END CERTIFICATE-----\n"
      }
    }
    1. Create the OpenVPN server:
    curl -s -k -u admin:pfsense -X POST https://pfsense-2.8.1-release.jaredhendrickson.com/api/v2/vpn/openvpn/server -H "content-type: application/json" -d '{"mode": "server_tls", "dev_mode": "tun", "protocol": "UDP4", "interface": "wan", "caref": "68d851be0a7ff", "certref": "68d853e619706", "dh_length": "2048", "ecdh_curve": "none", "data_ciphers": ["AES-256-GCM"], "data_ciphers_fallback": "AES-256-GCM", "digest": "SHA256", "use_tls": true, "tls_type": "auth"}'
    
    {
      "code": 200,
      "status": "ok",
      "response_id": "SUCCESS",
      "message": "",
      "data": {
        "id": 0,
        "vpnid": 1,
        "vpnif": "ovpns1",
        "description": "",
        "disable": false,
        "mode": "server_tls",
        "authmode": null,
        "dev_mode": "tun",
        "protocol": "UDP4",
        "interface": "wan",
        "local_port": "1194",
        "use_tls": true,
        "tls_type": "auth",
        "tlsauth_keydir": "default",
        "caref": "68d851be0a7ff",
        "certref": "68d853e619706",
        "cert_depth": 1,
        "dh_length": "2048",
        "ecdh_curve": "none",
        "data_ciphers": [
          "AES-256-GCM"
        ],
        "data_ciphers_fallback": "AES-256-GCM",
        "digest": "SHA256",
        "strictusercn": null,
        "remote_cert_tls": true,
        "tunnel_network": "",
        "tunnel_networkv6": "",
        "serverbridge_dhcp": null,
        "serverbridge_interface": null,
        "serverbridge_routegateway": null,
        "serverbridge_dhcp_start": null,
        "serverbridge_dhcp_end": null,
        "gwredir": false,
        "gwredir6": false,
        "local_network": [],
        "local_networkv6": [],
        "remote_network": [],
        "remote_networkv6": [],
        "maxclients": null,
        "allow_compression": "no",
        "passtos": false,
        "client2client": false,
        "duplicate_cn": false,
        "connlimit": null,
        "dynamic_ip": false,
        "topology": "subnet",
        "inactive_seconds": 300,
        "ping_method": "keepalive",
        "keepalive_interval": 10,
        "keepalive_timeout": 60,
        "ping_seconds": null,
        "ping_push": null,
        "ping_action": null,
        "ping_action_seconds": null,
        "ping_action_push": null,
        "dns_domain": null,
        "dns_server1": null,
        "dns_server2": null,
        "dns_server3": null,
        "dns_server4": null,
        "push_blockoutsidedns": null,
        "push_register_dns": null,
        "ntp_server1": null,
        "ntp_server2": null,
        "netbios_enable": null,
        "netbios_ntype": null,
        "netbios_scope": null,
        "wins_server1": null,
        "wins_server2": null,
        "custom_options": [],
        "username_as_common_name": null,
        "sndrcvbuf": null,
        "create_gw": "both",
        "verbosity_level": 1
      }
    }
    1. Create OpenVPN Client Export config for the server:
    curl -s -k -u admin:pfsense -X POST https://pfsense-2.8.1-release.jaredhendrickson.com/api/v2/vpn/openvpn/client_export/config -H "content-type: application/json" -d '{"server": 1}'
    
    {
      "code": 200,
      "status": "ok",
      "response_id": "SUCCESS",
      "message": "",
      "data": {
        "id": 0,
        "server": 1,
        "useaddr": "serveraddr",
        "useaddr_hostname": null,
        "verifyservercn": "auto",
        "blockoutsidedns": false,
        "legacy": false,
        "silent": false,
        "bindmode": "nobind",
        "usepkcs11": false,
        "pkcs11providers": null,
        "pkcs11id": null,
        "usetoken": false,
        "usepass": false,
        "p12encryption": "high",
        "useproxy": false,
        "useproxytype": null,
        "proxyaddr": null,
        "proxyport": null,
        "useproxypass": null,
        "proxyuser": null,
        "advancedoptions": ""
      }
    }
    1. Create a new VPN user:
    curl -k -u admin:pfsense -X POST https://pfsense-2.8.1-release.jaredhendrickson.com/api/v2/user -H "content-type: application/json" -d '{"name": "vpn_user", "password": "testpass"}'
    
    {
      "code": 200,
      "status": "ok",
      "response_id": "SUCCESS",
      "message": "",
      "data": {
        "id": 1,
        "name": "vpn_user",
        "uid": 2000,
        "scope": "user",
        "priv": [],
        "disabled": false,
        "descr": "",
        "expires": "",
        "cert": [],
        "authorizedkeys": "",
        "ipsecpsk": ""
      }
    }
    1. Create a user certificate for the VPN user:
    curl -k -u admin:pfsense -X POST https://pfsense-2.8.1-release.jaredhendrickson.com/api/v2/system/certificate/generate -H "content-type: application/json" -d '{"descr": "vpn_user_cert", "keytype": "RSA", "keylen": 2048, "digest_alg": "sha256", "lifetime": 365, "type": "server", "dn_commonname": "vpn_user", "caref": "68d851be0a7ff"}'
    
    {
      "code": 200,
      "status": "ok",
      "response_id": "SUCCESS",
      "message": "",
      "data": {
        "id": 2,
        "descr": "vpn_user_cert",
        "refid": "68d859f447964",
        "caref": "68d851be0a7ff",
        "keytype": "RSA",
        "keylen": 2048,
        "ecname": null,
        "digest_alg": "sha256",
        "lifetime": 365,
        "dn_commonname": "vpn_user",
        "dn_country": null,
        "dn_state": null,
        "dn_city": null,
        "dn_organization": null,
        "dn_organizationalunit": null,
        "type": "server",
        "dn_dns_sans": [],
        "dn_email_sans": [],
        "dn_ip_sans": [],
        "dn_uri_sans": [],
        "crt": "-----BEGIN CERTIFICATE-----\nMIIEWDCCA0CgAwIBAgIIa...nq6mJZiPGOM8RzNee\n-----END CERTIFICATE-----\n"
      }
    }
    1. Assign the certificate to the user:
    curl -s -k -u admin:pfsense -X PATCH https://pfsense-2.8.1-release.jaredhendrickson.com/api/v2/user -H "content-type: application/json" -d '{"id": 1, "cert": ["68d859f447964"]}'
    
    {
      "code": 200,
      "status": "ok",
      "response_id": "SUCCESS",
      "message": "",
      "data": {
        "id": 1,
        "name": "vpn_user",
        "uid": 2000,
        "scope": "user",
        "priv": [],
        "disabled": false,
        "descr": "",
        "expires": "",
        "cert": [
          "68d859f447964"
        ],
        "authorizedkeys": "",
        "ipsecpsk": ""
      }
    }
    1. Initiate the client export for the user
    curl -k -u admin:pfsense -X POST https://pfsense-2.8.1-release.jaredhendrickson.com/api/v2/vpn/openvpn/client_export -H "content-type: application/json" -H "accept: application/octet-stream" -d '{"id": 0, "type": "confzip", "username": "vpn_user", "certref": "68d859f447964"}' -o /tmp/export.zip
    

    Then I'm able to unzip the file and the expected contents are present:

    unzip /tmp/export.zip
    
    Archive:  export.zip
       creating: pfSense-UDP4-1194-vpn_user
     extracting: pfSense-UDP4-1194-vpn_user/pfSense-UDP4-1194-vpn_user-proxy
      inflating: pfSense-UDP4-1194-vpn_user/pfSense-UDP4-1194-vpn_user.p12
      inflating: pfSense-UDP4-1194-vpn_user/pfSense-UDP4-1194-vpn_user.ovpn
      inflating: pfSense-UDP4-1194-vpn_user/pfSense-UDP4-1194-vpn_user-tls.key
    

    Seems to work regardless of the number of certificates in the pfSense config or number of user certificates assigned to a user. I've attached the resulting config file as well.

    config.xml

    Anything I'm missing?

  3. cvanbrummelen commented on Sep 30, 2025

    @cvanbrummelen
    Author

    @jaredhendrickson13 Thanks for all the steps! Don't think you are missing anything. Ive checked all the steps myself and that is pretty much what I have.

    I also tried to redo the steps from step 6 to check if I need to create a user after setting the Client Export Config but no succes.

    Below the response I get from client_export/configs (Did it twice).

    {
        "code": 200,
        "status": "ok",
        "response_id": "SUCCESS",
        "message": "",
        "data": [
            {
                "id": 0,
                "server": 2,
                "useaddr": "serveraddr",
                "useaddr_hostname": "",
                "verifyservercn": "auto",
                "blockoutsidedns": false,
                "legacy": false,
                "silent": false,
                "bindmode": "nobind",
                "usepkcs11": false,
                "pkcs11providers": [],
                "pkcs11id": null,
                "usetoken": false,
                "usepass": false,
                "p12encryption": "high",
                "useproxy": false,
                "useproxytype": "http",
                "proxyaddr": "",
                "proxyport": "",
                "useproxypass": "none",
                "proxyuser": "",
                "advancedoptions": ""
            },
            {
                "id": 1,
                "server": 2,
                "useaddr": "serveraddr",
                "useaddr_hostname": null,
                "verifyservercn": "auto",
                "blockoutsidedns": false,
                "legacy": false,
                "silent": false,
                "bindmode": "nobind",
                "usepkcs11": false,
                "pkcs11providers": null,
                "pkcs11id": null,
                "usetoken": false,
                "usepass": false,
                "p12encryption": "high",
                "useproxy": false,
                "useproxytype": null,
                "proxyaddr": null,
                "proxyport": null,
                "useproxypass": null,
                "proxyuser": null,
                "advancedoptions": ""
            }
        ]
    }
    

    And the body I send to client_export

    {
        "id": 0, 
        "type": "confzip",
        "username": "TESTUSER", 
        "certref": "68dbe6fa99XXX"
    }
    

    Response:

    {
        "code": 500,
        "status": "internal server error",
        "response_id": "OPENVPN_CLIENT_EXPORT_CREATION_FAILED_FOR_KNOWN_REASON",
        "message": "The OpenVPN client export failed for the following reason: Could not find client certificate.",
        "data": []
    }
    

    Also a screenshot to show that the certificate is connected to the user:

    Image
  4. jaredhendrickson13 commented on Sep 30, 2025

    @jaredhendrickson13
    Member

    Hmm that is very odd. What do the system/user, cert and ca sections of your XML config look like?

  5. cvanbrummelen commented on Oct 1, 2025

    @cvanbrummelen
    Author

    Below the parts of the XML, I did remove some stuff, if you need more let me know.

    System/User:

    Image

    Cert part for TESTUSER

    Image

    CA:

    Image
  6. jaredhendrickson13 commented on Oct 4, 2025

    @jaredhendrickson13
    Member

    Thanks for your patience and providing all this info. I do believe I see the issue now and can reproduce. The pfSense export functions unnecessarily change the context of the certificate's ID for several different factors which the API is not fully accounting for. The $crtid sometimes refers to the index of system/user/cert field, but usually refers to the index of cert (which is what the API is expecting). It's a little silly since the actual certificate has to be looked up at cert anyway, so looking up the certificate at system/user/cert first is just an unnecessarily complicated step.

    Regardless, I'll get working on a fix and will have it in the next patch. Hopefully this weekend or early next week.

    Thanks again for reporting this!

  7. added
    bugSomething isn't working
    and removed
    troubleshootingIssues that are troubleshooting requests or need troubleshooting before proceeding
    on Oct 4, 2025
  8. cvanbrummelen commented on Oct 6, 2025

    @cvanbrummelen
    Author

    Thanks! I will wait for the fix and get on it when its out.

  9. linked a pull request that will close this issuev2.6.3 Fixes #765on Oct 6, 2025
  10. cvanbrummelen commented on Oct 8, 2025

    @cvanbrummelen
    Author

    @jaredhendrickson13 Fix works perfectly! Thanks for the work and the fast response!

  11. lapf10 commented on Nov 13, 2025

    @lapf10

    Hi,

    Thanks for great work on this projet.
    I try to export my OpenVPN Client but without success.
    I do not have users created at Pfsense, my users are from LDAP.

    I follow the steps metioned before and i get the error below

    1 -> POST /api/v2/vpn/openvpn/client_export/config

    Image

    2 -> Response GET /api/v2/vpn/openvpn/client_export/configs

    Image

    3 -> Try Export POST /api/v2/vpn/openvpn/client_export

    Image

    Can you please help to solve this problem.

    Thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

bugSomething isn't working

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions