Skip to main content
Skip table of contents

PKI Use Case: Issue Certificates using PQC-ready Protocols

After completing Initial Setup of PQC Lab Test Drive, you are ready to start generating PQC certificates using enrollment protocols such as CMP, EST, ACME, and the EJBCA REST API.

This how-to guide provides step-by-step instructions for each protocol, showing you how to issue PQC certificates, view them in EJBCA, and validate the results.

The video tutorial was recorded with an older version of the PQC Lab, with a different VM, different ports, etc. For the correct details of the new PQC Lab, see below on this page.

Prerequisites

  • A work directory with the extracted clients packages

  • OpenSSL 3.5 or later is used for the validation. OpenSSL must be installed to follow parts of this tutorial that use the OpenSSL commands.

    CODE
    bash-5.2$ openssl version
    OpenSSL 3.6.0 1 Oct 2025 (Library: OpenSSL 3.6.0 1 Oct 2025)
  • Curl

    CODE
    bash-5.2$ curl --version
    curl 8.7.1 (x86_64-apple-darwin24.0) libcurl/8.7.1 (SecureTransport) LibreSSL/3.3.6 zlib/1.2.12 nghttp2/1.64.0
  • In your terminal, prepare variables for easier access

    • (Replace with your Test Drive hostname, OAuth username and password)

    • CODE
      $ TEST_DRIVE_HOST=pqclabay22eww86kd0.eastus2.cloudapp.azure.com
    • CODE
      $ TEST_DRIVE_OAUTH_USERNAME=admino1cqf
    • CODE
      $ TEST_DRIVE_OAUTH_PASSWD="<your-password>"

Issue PQC certificates

The following provides steps to enroll for a certificate using the enrollment protocols CMP, ACME, EST, and EJBCA REST API.

Create a CRMF Request​ and Issue a Certificate using CMP

When issuing an end entity certificate from a PQC CA, the certificate will also be PQC, meaning that it is signed by a PQC CA key.

The certificate request will include indirect Proof-of-Possession, meaning that the certificate is sent back encrypted to the client. For more information, see RFC 4211.

To create a Certificate Request Message Format (CRMF) and issue a certificate using the EJBCA cmpclient, follow these steps:

  1. Navigate to the ejbca-clients root

CODE
$ cd <testDriveWorkDir>/clients/ejbca-clients
  1. Create a new directory for the CMP enrollment:

CODE
$ mkdir cmp && cd cmp
  1. Create a CRMF, using the CMP Alias URL, the EndEntity Common Name and Password for the HMAC authentication, the requested key pair for the key encapsulation mechanism to ML-DSA-44, and the includedpopo flag to include proof of possession:

CODE
../ejbca-clients/dist/cmpclient/cmpclient.sh crmf \
  --url "http://$TEST_DRIVE_HOST:8080/ejbca/publicweb/cmp/pqcclient" \
  --dn "CN=cmpencert" \
  --authparam foo123 \
  --reqnewkeyspec ML-DSA-44 \
  --includepopo \
  -- dest . \
  --v
  1. The output is similar to the following. Note that the certificate is decrypted with the ML-KEM-512 private key:

CODE
2025-10-30 22:19:36,833+0100 INFO  [org.ejbca.ui.cmpclient.commands.CrmfRequestCommand] (main) Creating CRMF request with: SubjectDN=CN=cmpencert
2025-10-30 22:19:36,835+0100 INFO  [org.ejbca.ui.cmpclient.commands.CrmfRequestCommand] (main) Creating CRMF request with: IssuerDN=null
2025-10-30 22:19:36,835+0100 INFO  [org.ejbca.ui.cmpclient.commands.CrmfRequestCommand] (main) Creating CRMF request with: AuthenticationModule=null
2025-10-30 22:19:36,835+0100 INFO  [org.ejbca.ui.cmpclient.commands.CrmfRequestCommand] (main) Creating CRMF request with: EndEntityPassword=
2025-10-30 22:19:36,835+0100 INFO  [org.ejbca.ui.cmpclient.commands.CrmfRequestCommand] (main) Creating CRMF request with: SubjectAltName=null
2025-10-30 22:19:36,835+0100 INFO  [org.ejbca.ui.cmpclient.commands.CrmfRequestCommand] (main) Creating CRMF request with: CustomCertSerno=
2025-10-30 22:19:36,835+0100 INFO  [org.ejbca.ui.cmpclient.commands.CrmfRequestCommand] (main) Creating CRMF request with: IncludePopo=true
2025-10-30 22:19:36,835+0100 INFO  [org.ejbca.ui.cmpclient.commands.CrmfRequestCommand] (main) Creating CRMF request with: requestedValidity=null
2025-10-30 22:19:36,835+0100 INFO  [org.ejbca.ui.cmpclient.commands.CrmfRequestCommand] (main) Creating CRMF request with: keyID=null
2025-10-30 22:19:36,879+0100 INFO  [org.ejbca.ui.cmpclient.commands.CrmfRequestCommand] (main) Added POP of type: 1 - signature
2025-10-30 22:19:36,937+0100 INFO  [org.apache.commons.beanutils.FluentPropertyBeanIntrospector] (main) Error when creating PropertyDescriptor for public final void org.apache.commons.configuration2.AbstractConfiguration.setProperty(java.lang.String,java.lang.Object)! Ignoring this property.
2025-10-30 22:19:36,967+0100 INFO  [com.keyfactor.util.RandomHelper] (main) Using FIPS/SP800 compliant Bouncy Castle Hybrid serialNumber RNG algorithm.
2025-10-30 22:19:36,969+0100 INFO  [org.ejbca.ui.cmpclient.commands.CrmfRequestCommand] (main) Using default authentication module: HMAC
2025-10-30 22:19:36,970+0100 INFO  [org.ejbca.ui.cmpclient.CmpClientMessageHelper] (main) Creating protected PKIMessage using: authentication module=HMAC, authentication parameter=foo123
2025-10-30 22:19:36,991+0100 INFO  [org.ejbca.ui.cmpclient.CmpClientMessageHelper] (main) Using CMP URL: http://pqclabay22eww86kd0.eastus2.cloudapp.azure.com:8080/ejbca/publicweb/cmp/pqcclient
2025-10-30 22:19:37,487+0100 INFO  [org.ejbca.ui.cmpclient.commands.CrmfRequestCommand] (main) CRMF request successful. Received certificate stored in ./cmpencert.pem
2025-10-30 22:19:37,494+0100 INFO  [org.ejbca.ui.cmpclient.commands.CrmfRequestCommand] (main) Generated private key stored in ./cmpencert-key.pem

You have now issued an end entity certificate and can view it using OpenSSL.

Use EST to Enroll for a Certificate

The Test Drive has two preconfigured EST aliases, Client Mode, and RA Mode.

To generate a CSR and enroll for a certificate using the Enrollment over Secure Transport (EST) protocol with basic authentication, follow these steps:

  1. Open a terminal window and change to the unzipped EJBCA Client CLI directory.

    CODE
    $ cd <testDriveWorkDir>/clients/ejbca-clients
  2. Create a new directory for the EST enrollment:

    CODE
    $ mkdir est && cd est
  3. Generate a CSR:

    CODE
    $ ../ejbca-clients/bin/ejbca.sh gencsr \
      --keyalg ML-DSA-44 \
      --subjectdn "C=ZZ,O=Keyfactor PQC Lab,OU=Devices,CN=pqc-EstTestDevice01"
  4. Inspect the CSR:

    CODE
    $ openssl req -in certificateSigningRequest.csr -text -noout
  5. Convert the CSR to DER encoding: 

    CODE
    $ openssl req \
      -inform PEM \
      -outform DER \
      -in certificateSigningRequest.csr \
      -out pqc-estTestDevice01.csr
  6. Download the ML-DSA-Sub-G1 certificate: 

    CODE
    $ curl -k \
      https://$TEST_DRIVE_HOST:8443/.well-known/est/pqcra/cacerts \
      -o cacerts.p7
  7. Base64 encode the CSR: 

    CODE
    $ openssl base64 -in pqc-estTestDevice01.csr -out pqc-estTestDevice01.b64 -e
  8. Enroll for a certificate using the EST protocol with basic authentication: 

    CODE
    $ curl -kv \
      --user "pqcclient:foo123" \
      --data @pqc-estTestDevice01.b64 \
      -o pqc-estTestDevice01-p7.b64 \
      -H "Content-Type: application/pkcs10" \
      -H "Content-Transfer-Encoding: base64" \
      https://$TEST_DRIVE_HOST:8443/.well-known/est/pqcra/simpleenroll
  9. Convert the EST response from base64-encoded PKCS7 (.b64) file into a PEM:

    CODE
    $ cat pqc-estTestDevice01-p7.b64 | \
      base64 --decode | \
      openssl pkcs7 \
        -inform DER \
        -print_certs \
        -out pqc-estTestDevice01.pem
  10. Review the certificate with OpenSSL:

    CODE
    $ openssl x509 -noout -text -in pqc-estTestDevice01.pem

Use ACME to Enroll for a Certificate

To generate a CSR and enroll for a certificate using the Automatic Certificate Management Environment (ACME) protocol and the ACME Certbot client, follow these steps:

  1. Change to ejbca-clients root

BASH
$ cd <testDriveWorkDir>/clients/ejbca-clients
  1. Create a new directory for the ACME enrollment:

BASH
$ mkdir acme && cd acme
  1. Generate a CSR:

BASH
$ ../ejbca-clients/bin/ejbca.sh gencsr \
  --keyalg ML-DSA-44 \
  --subjectdn "C=ZZ,O=Keyfactor PQC Lab,OU=Devices,CN=<DNS-RESOLVABLE-FQDN>" \
  --subjectaltname "dNSName=<DNS-RESOLVABLE-FQDN>"
  1. Copy the CSR to a host that has an ACME client such as Certbot installed using SCP.

  2. SSH to an ACME host.

  3. Enroll for a certificate using the ACME certbot client:

BASH
$ sudo certbot certonly \
  --standalone \
  --server https://$TEST_DRIVE_HOST:8443/ejbca/acme/pqc-ml-dsa/directory \
  --agree-tos \
  --email your.email@yourdomain.com \
  --no-eff-email \
  --no-verify-ssl \
  --csr certificateSigningRequest.csr
  1. Parse the certificate with openssl: 

CODE
 $ openssl x509 -noout -text -in 0000_cert.pem

Use REST API to Enroll for a Certificate

Any RESTful client supporting certificate or OAuth authentication is suitable.

OAuth authentication via curl is used in the example.

To generate a CSR and enroll for a certificate using the EJBCA REST API, follow these steps:

  1. Change to ejbca-clients root

    CODE
    $ cd <testDriveWorkDir>/clients/ejbca-clients
  2. Create a new directory for the REST API enrollment: 

    CODE
    $ mkdir rest && cd rest
  3. Generate a CSR: 

    CODE
    $ ../ejbca-clients/bin/ejbca.sh gencsr \
      --keyalg ML-DSA-44 \
      --subjectdn "C=ZZ,O=Keyfactor PQC Lab,OU=Devices,CN=pqc-RestTestDevice01"
  4. Convert the CSR to DER, base64-encode it (without newlines), and store it in csr_in_der_b64

    CODE
    $ csr_in_der_b64=$(
      openssl req \
        -outform DER \
        -in certificateSigningRequest.csr | \
      base64 | \
      tr -d '[:space:]'
    )
  5. Get OAuth access token from the IDP of your Test Drive

    CODE
    $ TOKEN=$(
      curl -sk -X POST "https://$TEST_DRIVE_HOST:8444/realms/Keyfactor/protocol/openid-connect/token" \
        -d "grant_type=password" \
        --data-urlencode "client_id=ejbca" \
        --data-urlencode "client_secret=$TEST_DRIVE_OAUTH_PASSWD" \
        --data-urlencode "username=$TEST_DRIVE_OAUTH_USERNAME" \
        --data-urlencode "password=$TEST_DRIVE_OAUTH_PASSWD" \
      | jq -r .access_token
    )
  6. Enroll for a certificate using pkcs10enroll endpoint

    CODE
    $ RESPONSE=$(
      curl -k -s -X POST "https://$TEST_DRIVE_HOST:8443/ejbca/ejbca-rest-api/v1/certificate/pkcs10enroll" \
        -H "Authorization: Bearer $TOKEN" \
        -H "Content-Type: application/json" \
        -H "Accept: application/json" \
        --data "{
          \"certificate_request\": \"$csr_in_der_b64\",
          \"certificate_profile_name\": \"ml-dsa-DeviceAuthentication-1y\",
          \"end_entity_profile_name\": \"pqcDeviceAuth\",
          \"certificate_authority_name\": \"ML-DSA-Sub-G1\",
          \"username\": \"usermldsa44\",
          \"password\": \"usermldsa44\",
          \"include_chain\": \"true\"
        }"
    )
  7. Extract the base64 cert from the response and convert directly to PEM

    CODE
    $ echo "$RESPONSE" | \
      jq -r '.certificate' | \
      base64 -d | \
      openssl x509 -inform DER -out pqc-RestTestDevice01.pem
  8. Parse the certificate with openssl: 

    CODE
    $ openssl x509 -text -noout -in pqc-RestTestDevice01.pem

You have now obtained ML-DSA-44 certificates out of the enrollment protocols ACME, EST, and EJBCA REST API.

View issued certificates

To view the PQC certificates you have issued, do the following:

  1. Navigate to the Test Drive start page.

  2. Click the EJBCA Administration Web link.

  3. On the EJBCA administration page, click the menu option RA Web.

  4. Click Search > Certificates and enter PQC in the search field.

  5. The issued PQC certificates are listed in the overview.

You can display detailed information about the certificates by clicking View.

Next steps

You can also explore the PQC Lab signing use case:

To learn more about post-quantum cryptography and how you should prepare, see Post-Quantum Readiness

Contact us

Schedule a live demo with one of our experts to discuss your environment, integration options, and how we can help you move from test drive to production.

Request a Demo

JavaScript errors detected

Please note, these errors can depend on your browser setup.

If this problem persists, please contact our support.