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.
CODEbash-5.2$ openssl version OpenSSL 3.6.0 1 Oct 2025 (Library: OpenSSL 3.6.0 1 Oct 2025)Curl
CODEbash-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.0In 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>"
If you haven’t already done this, download clients.zip and unzip it in a new work directory. For details, see Initial Setup of PQC Lab Test Drive.
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:
Navigate to the ejbca-clients root
$ cd <testDriveWorkDir>/clients/ejbca-clients
Create a new directory for the CMP enrollment:
$ mkdir cmp && cd cmp
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:
../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
The output is similar to the following. Note that the certificate is decrypted with the ML-KEM-512 private key:
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:
Open a terminal window and change to the unzipped EJBCA Client CLI directory.
CODE$ cd <testDriveWorkDir>/clients/ejbca-clientsCreate a new directory for the EST enrollment:
CODE$ mkdir est && cd estGenerate 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"Inspect the CSR:
CODE$ openssl req -in certificateSigningRequest.csr -text -nooutConvert the CSR to DER encoding:
CODE$ openssl req \ -inform PEM \ -outform DER \ -in certificateSigningRequest.csr \ -out pqc-estTestDevice01.csrDownload the ML-DSA-Sub-G1 certificate:
CODE$ curl -k \ https://$TEST_DRIVE_HOST:8443/.well-known/est/pqcra/cacerts \ -o cacerts.p7Base64 encode the CSR:
CODE$ openssl base64 -in pqc-estTestDevice01.csr -out pqc-estTestDevice01.b64 -eEnroll 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/simpleenrollConvert 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.pemReview 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:
Change to ejbca-clients root
$ cd <testDriveWorkDir>/clients/ejbca-clients
Create a new directory for the ACME enrollment:
$ mkdir acme && cd acme
Generate a CSR:
$ ../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>"
Copy the CSR to a host that has an ACME client such as Certbot installed using SCP.
SSH to an ACME host.
Enroll for a certificate using the ACME certbot client:
$ 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
Parse the certificate with openssl:
$ 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:
Change to ejbca-clients root
CODE$ cd <testDriveWorkDir>/clients/ejbca-clientsCreate a new directory for the REST API enrollment:
CODE$ mkdir rest && cd restGenerate 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"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:]' )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 )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\" }" )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.pemParse 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:
Navigate to the Test Drive start page.
Click the EJBCA Administration Web link.
On the EJBCA administration page, click the menu option RA Web.
Click Search > Certificates and enter PQC in the search field.
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.