Signing Use Case: Perform PQC Signing and Validation
After completing the Initial Setup of PQC Lab Test Drive, you are ready to start testing post-quantum signing and validation operations using SignServer.
This page provides a step-by-step guide and example commands for signing and verifying files, hashes, and software artifacts using PQC algorithms. You will learn how to perform signing and timestamping with SignServer through the CLI and REST API, and how to validate PQC signatures using OpenSSL and other verification tools.
Prerequisites
OpenSSL 3.5 or later is used for the validation. OpenSSL must be installed to follow parts of this tutorial that use the OpenSSL command.
CODEbash-5.2$ openssl version OpenSSL 3.6.0 1 Oct 2025 (Library: OpenSSL 3.6.0 1 Oct 2025)JQ installed for parsing json data
CODEbash-5.2$ which jq /usr/bin/jqJQ & OpenSSL should be in the
PATHvariable on Linux or system path on Windows.If you haven’t already done this, download clients.zip and signer_certs.zip and unzip them in the same work directory. For details, see Initial Setup of PQC Lab Test Drive.
Download ManagementCA JKS truststore from EJBCA RA Web → CA Certificates and CRLs → Certificate chain, and place in the same directory as
clientsandsigner_certs.Replace the variables with the applicable values for the commands below:
Linux:
TEST_DRIVE_HOST: the fully qualified domain name (FQDN) of the test drive host; e.g. exampletestdrive.eastus2.cloudapp.azure.comCODE$ TEST_DRIVE_HOST=pqclabpbi213zqo8w3.eastus2.cloudapp.azure.comTEST_DRIVE_OAUTH_USERNAME: the admin username provided in the Test Drive portal for accessing the test drive using OAuth; e.g. admin57g9nCODE$ TEST_DRIVE_OAUTH_USERNAME=admin57g9nTEST_DRIVE_OAUTH_PASSWD: the admin password provided in the Test Drive portal for accessing the test drive using OAuthCODE$ TEST_DRIVE_OAUTH_PASSWD=<your-password>
Windows:
- CODE
$env:TEST_DRIVE_HOST = "pqclabpbi213zqo8w3.eastus2.cloudapp.azure.com" $env:TEST_DRIVE_OAUTH_USERNAME = "admin57g9n" $env:TEST_DRIVE_OAUTH_PASSWD = "<your-password>"
Create
testFilesdirectory inclientsand place sample files of type txt, msi, cs, deb, and jar.CODEbash-5.2$ cd <testDriveWorkDir>/clients bash-5.2$ mkdir testFilesTo begin testing, open a terminal on Linux/MacOS or Command Prompt terminal with PowerShell on Windows. Change to the directory where you unzipped the Client CLI's.
CODE$ cd <testDriveWorkDir>/clients/signserver
PlainSigner PQC Test
Use the PlainSigner to get a detached signature for the hash of the artifact.
Signclient Test
Test signing with the PlainSigner worker by sending the file with signclient to SignServer and receiving a detached signature file. Client-side hashing is not supported for PQC algorithms at this time.
Sign file with PlainSignerPQC
Chang to signserver directory under clients
Sign the HelloDeb.txt text file:
Linux:
bin/signclient signdocument \
-host "$TEST_DRIVE_HOST" \
-port 8445 \
-workername PlainSignerPqc \
-truststore ../signer_certs/ManagementCA-chain.jks \
-truststorepwd changeit \
-infile ../testFiles/HelloDeb.txt \
-outfile ../testFiles/HelloDeb.txt.sig
Windows:
bin/signclient signdocument `
-host $env:TEST_DRIVE_HOST `
-port 8445 `
-workername PlainSignerPqc `
-truststore ..\signer_certs\ManagementCA-chain.jks `
-truststorepwd changeit `
-infile ..\testFiles\HelloDeb.txt `
-outfile ..\testFiles\HelloDeb.txt.sig
Timestamp Plain Signed file
Timestamp the HelloDeb.txt signature file:
Linux:
bin/signclient timestamp \
-url "https://$TEST_DRIVE_HOST:8445/signserver/tsa?workerName=TimeStampSignerPqc" \
-infile ../testFiles/HelloDeb.txt.sig \
-outrep ../testFiles/HelloDeb.txt.sig.ts \
-truststore ../signer_certs/ManagementCA-chain.jks \
-truststorepwd changeit
Windows:
bin/signclient timestamp `
-url "https://$env:TEST_DRIVE_HOST:8445/signserver/tsa?workerName=TimeStampSignerPqc" `
-infile ..\testFiles\HelloDeb.txt.sig `
-outrep ..\testFiles\HelloDeb.txt.sig.ts `
-truststore ..\signer_certs\ManagementCA-chain.jks `
-truststorepwd changeit
Verify PlainSigner PQC signed file
Extract the publicKey from the PlainSigner PQC certificate file
$ openssl x509 \
-in ../signer_certs/PlainSignerPqc.crt -noout \
-pubkey > ../signer_certs/PlainSignerPqc.pubkey
Verify the signature with OpenSSL
Linux:
$ openssl pkeyutl -verify \
-pubin -inkey ../signer_certs/PlainSignerPqc.pubkey \
-sigfile ../testFiles/HelloDeb.txt.sig \
-in ../testFiles/HelloDeb.txt
Windows:
openssl pkeyutl -verify `
-pubin -inkey ..\signer_certs\PlainSignerPqc.pubkey `
-sigfile ..\testFiles\HelloDeb.txt.sig `
-in ..\testFiles\HelloDeb.txt
Signserver PlainSigner Rest API Test
The Rest API can be used to send a hash created locally or to send the file to SignServer to get signed.
Sign and verify a file with the Rest API PlainSigner
Use the SignServer Rest API to get the file signed with the PQC PlainSigner
Linux:
$ curl -ksL -X POST \
"https://$TEST_DRIVE_HOST:8445/signserver/rest/v1/workers/PlainSignerPqc/process" \
-H "X-Keyfactor-Requested-With: X" \
-H "Accept: application/octet-stream" \
--form 'myfile=@"../testFiles/HelloDeb.txt";type=multipart/form-data;charset=utf-8' \
-o ../testFiles/HelloDeb.txt-via-rest.sig
Windows:
curl -ksL -X POST `
"https://$env:TEST_DRIVE_HOST:8445/signserver/rest/v1/workers/PlainSignerPqc/process" `
-H "X-Keyfactor-Requested-With: X" `
-H "Accept: application/octet-stream" `
--form "myfile=@..\testFiles\HelloDeb.txt;type=multipart/form-data;charset=utf-8" `
-o ..\testFiles\HelloDeb.txt-via-rest.sig
Verify the signature with OpenSSL
Linux:
$ openssl pkeyutl -verify \
-pubin -inkey ../signer_certs/PlainSignerPqc.pubkey \
-sigfile ../testFiles/HelloDeb.txt-via-rest.sig \
-in ../testFiles/HelloDeb.txt
Signature Verified Successfully
Windows:
openssl pkeyutl -verify `
-pubin `
-inkey ..\signer_certs\PlainSignerPqc.pubkey `
-sigfile ..\testFiles\HelloDeb.txt-via-rest.sig `
-in ..\testFiles\HelloDeb.txt
Sign and verify a hash with Rest API PlainSigner
Hash a file to sign, and save the hash to the variable
PS_HASH
Linux:
$ PS_HASH=$(openssl dgst -sha384 -binary ../testFiles/HelloDeb.txt | base64)
Windows:
PS_HASH = (openssl dgst -sha384 -binary ..\testFiles\HelloDeb.txt | openssl base64 -A)
Sign the hash using Rest API and save the result as json
Linux:
curl -ksL -X POST \
"https://$TEST_DRIVE_HOST:8445/signserver/rest/v1/workers/PlainSignerPqc/process" \
-H "X-Keyfactor-Requested-With: X" \
-H "Content-Type: application/json" \
-d "{\"data\": \"$PS_HASH\",\"encoding\": \"BASE64\",\"metaData\": {\"USING_CLIENTSUPPLIED_HASH\": \"true\",\"CLIENTSIDE_HASHDIGESTALGORITHM\": \"SHA384\"}}" \
| jq . > ../testFiles/HelloDeb.txt-rest-hash.sig
Windows:
curl -ksL -X POST `
"https://$env:TEST_DRIVE_HOST:8445/signserver/rest/v1/workers/PlainSignerPqc/process" `
-H "X-Keyfactor-Requested-With: X" `
-H "Content-Type: application/json" `
-d "{\"data\": \"$env:PS_HASH\",\"encoding\": \"BASE64\",\"metaData\": {\"USING_CLIENTSUPPLIED_HASH\": \"true\",\"CLIENTSIDE_HASHDIGESTALGORITHM\": \"SHA384\"}}" `
| jq . > ..\testFiles\HelloDeb.txt-rest-hash.sig
Extract the signature of the file from the json file
Linux:
$ cat ../testFiles/HelloDeb.txt-signed.json \
| jq -r .data \
| base64 -d > ../testFiles/HelloDeb.txt-rest-hash.sig
Windows (with jq)
Get-Content ..\testFiles\HelloDeb.txt-signed.json | `
jq -r .data | `
base64 -d > ..\testFiles\HelloDeb.txt-rest-hash.sig
Windows (Powershell builtin json)
$json = Get-Content ..\testFiles\HelloDeb.txt-signed.json -Raw | ConvertFrom-Json
[IO.File]::WriteAllBytes(
"..\testFiles\HelloDeb.txt-rest-hash.sig",
[Convert]::FromBase64String($json.data)
)
Verify the signature with openSSL
Linux:
openssl pkeyutl -verify \
-pubin \
-inkey ../signer_certs/plainSignerPqc.pubkey \
-sigfile ../testFiles/HelloDeb.txt-rest-hash.sig \
-in <(openssl dgst -sha384 -binary ../testFiles/HelloDeb.txt)
Signature Verified Successfully
Windows:
openssl dgst -sha384 -binary ..\testFiles\HelloDeb.txt | `
openssl pkeyutl -verify `
-pubin `
-inkey ..\signer_certs\plainSignerPqc.pubkey `
-sigfile ..\testFiles\HelloDeb.txt-rest-hash.sig
Time Stamp PQC Test
Timestamp and verify artificat
The signclient is used to send an artifact to SignServer to be timestamped.
Time stamp one of the previous signatures created with the PlainSigner:
Linux:
bin/signclient timestamp \
-url "https://$TEST_DRIVE_HOST:8445/signserver/tsa?workerName=TimeStampSignerPqc" \
-infile ../testFiles/HelloDeb.txt-rest-hash.sig \
-base64 \
-outrep ../testFiles/HelloDeb.txt-rest-hash.sig.ts \
-truststore ../signer_certs/ManagementCA-chain.jks \
-truststorepwd changeit
Windows:
bin\signclient.cmd timestamp `
-url "https://$env:TEST_DRIVE_HOST:8445/signserver/tsa?workerName=TimeStampSignerPqc" `
-infile ..\testFiles\HelloDeb.txt-rest-hash.sig `
-base64 `
-outrep ..\testFiles\HelloDeb.txt-rest-hash.sig.ts `
-truststore ..\signer_certs\ManagementCA-chain.jks `
-truststorepwd changeit
Verify the response using signclient
Linux:
$ bin/signclient timestamp \
-verify \
-inrep ../testFiles/HelloDeb.txt-rest-hash.sig.ts \
-signerfile ../signer_certs/TimeStampSignerPqc.crt
2025-10-24T22:07:21,414 INFO [TimeStampCommand] Token was validated successfully.
2025-10-24T22:07:21,424 INFO [TimeStampCommand] Token was generated on: Fri Oct 24 21:57:12 CEST 2025
2025-10-24T22:07:21,424 INFO [TimeStampCommand] MessageDigest=c1265ee7d642fe56780e96924bccf3ca8e9f39d75875d6923a3005641a3f2ece
2025-10-24T22:07:21,425 INFO [TimeStampCommand] Processing took 116 ms
Windows:
bin\signclient.cmd timestamp `
-verify `
-inrep ..\testFiles\HelloDeb.txt-rest-hash.sig.ts `
-signerfile ..\signer_certs\TimeStampSignerPqc.crt
Verify the response using OpenSSL
Linux:
openssl ts -verify \
-in ../testFiles/HelloDeb.txt-rest-hash.sig.ts \
-data ../testFiles/HelloDeb.txt-rest-hash.sig \
-CAfile ../signer_certs/PqcCaChain.crt \
-signer ../signer_certs/TimeStampSignerPqc.crt
Windows:
openssl ts -verify `
-in ..\testFiles\HelloDeb.txt-rest-hash.sig.ts `
-data ..\testFiles\HelloDeb.txt-rest-hash.sig `
-CAfile ..\signer_certs\PqcCaChain.crt `
-signer ..\signer_certs\TimeStampSignerPqc.crt
Code Signer PQC Test
Sign msi file with Code Signer PQC and verify
Sign the msi file with signclient
Linux:
$ bin/signclient signdocument \
-host $TEST_DRIVE_HOST \
-port 8445 \
-workername MSAuthCodeSignerPqc \
-truststore ../signer_certs/ManagementCA-chain.jks \
-truststorepwd changeit \
-infile ../testFiles/sample.msi \
-outfile ../testFiles/sample-signed.msi
2025-10-25T00:55:14,593 INFO [HostManager] Next host retrieved for signing: pqclabpbi213zqo8w3.eastus2.cloudapp.azure.com
2025-10-25T00:55:29,824 INFO [SignDocumentCommand] Wrote ../testFiles/sample-signed.msi.
Windows:
bin/signclient signdocument `
-host $env:TEST_DRIVE_HOST `
-port 8445 `
-workername MSAuthCodeSignerPqc `
-truststore ..\signer_certs\ManagementCA-chain.jks `
-truststorepwd changeit `
-infile ..\testFiles\sample.msi `
-outfile ..\testFiles\sample-signed.msi
If using Linux copy the signed MSI file to a Windows device (Server, desktop, VM, etc).
In the Windows environment right click on the signed file and click Properties and then the Digital Signature tab.
Click the Details button to see the Digital Signature Information.
Jar Signer PQC Test
Sign file with Jar Signer PQC and verify
Use signclient to sign the jar file
Linux:
bin/signclient signdocument \
-host $TEST_DRIVE_HOST \
-port 8445 \
-workername JarSignerPqc \
-truststore ../signer_certs/ManagementCA-chain.jks \
-truststorepwd changeit \
-infile ../testFiles/HelloJar.jar \
-outfile ../testFiles/HelloJar-signed.jar
Windows:
bin/signclient signdocument `
-host $env:TEST_DRIVE_HOST `
-port 8445 `
-workername JarSignerPqc `
-truststore ..\signer_certs\ManagementCA-chain.jks `
-truststorepwd changeit `
-infile ..\testFiles\HelloJar.jar `
-outfile ..\testFiles\HelloJar-signed.jar
Linux:
2. Verify the signed jar file with jarsigner
jarsigner -verify \
../testFiles/HelloJar-signed.jar
Windows:
jarsigner -verify \
..\testFiles\HelloJar-signed.jar
NOTE: Jarsigner is not able to parse ML-DSA signatures and verification will fail
CMS Signer PQC Test
Sign file with CMS Signer PQC using signclient and verify
Sign the file with signclient
Linux:
$ bin/signclient signdocument \
-host $TEST_DRIVE_HOST \
-port 8445 \
-workername CmsSignerPqc \
-truststore ../signer_certs/ManagementCA-chain.jks \
-truststorepwd changeit \
-infile ../testFiles/HelloDeb.deb \
-outfile ../testFiles/HelloDeb-signed.deb
Windows:
bin\signclient signdocument `
-host $env:TEST_DRIVE_HOST `
-port 8445 `
-workername CmsSignerPqc `
-truststore ..\signer_certs\ManagementCA-chain.jks `
-truststorepwd changeit `
-infile ..\testFiles\HelloDeb.deb `
-outfile ..\testFiles\HelloDeb-signed.deb
Verify the file with OpenSSL
Linux:
$ openssl cms -verify \
-in ../testFiles/HelloDeb-signed.deb \
-inform DER \
-content ../testFiles/HelloDeb.deb \
-CAfile ../signer_certs/PqcCaChain.crt \
-binary \
> /dev/null
CMS Verification successful
Windows:
openssl cms -verify `
-in ..\testFiles\HelloDeb-signed.deb `
-inform DER `
-content ..\testFiles\HelloDeb.deb `
-CAfile ..\signer_certs\PqcCaChain.crt `
-binary `
> $null
SignServer CMS Rest API Test and verify
When Using Windows user PowerShell in Command Prompt.
Select a file to sign, and get the hash of the file:
Linux:
CMS_HASH=$(openssl dgst -sha384 -binary ../testFiles/HelloPE.cs | base64)
Windows:
$CMS_HASH=(openssl dgst -sha384 -binary .\testFiles\HelloPE.cs | openssl base64 -A)
Use the SignServer Rest API to get the hash signed with the PQC CMS Signer
Linux:
$ curl -ksL -X POST \
"https://$TEST_DRIVE_HOST:8445/signserver/rest/v1/workers/CmsSignerPqc/process" \
-H "X-Keyfactor-Requested-With: X" \
-H "Content-Type: application/json" \
-d "{\"data\": \"$CMS_HASH\",\"encoding\": \"BASE64\",\"metaData\": {\"USING_CLIENTSUPPLIED_HASH\": \"true\",\"CLIENTSIDE_HASHDIGESTALGORITHM\": \"SHA384\"}}" \
| jq . > ../testFiles/HelloPE.cs.json
Windows:
curl -ksL -X POST `
"https://$env:TEST_DRIVE_HOST:8445/signserver/rest/v1/workers/CmsSignerPqc/process" `
-H "X-Keyfactor-Requested-With: X" `
-H "Content-Type: application/json" `
-d "{\"data\": \"$env:CMS_HASH\",\"encoding\": \"BASE64\",\"metaData\": {\"USING_CLIENTSUPPLIED_HASH\": \"true\",\"CLIENTSIDE_HASHDIGESTALGORITHM\": \"SHA384\"}}" `
| jq . > ..\testFiles\HelloPE.cs.json
Extract the signature of the file from the json file
Linux:
cat ../testFiles/HelloPE.cs.json \
| jq -r .data \
| base64 -d \
> ../testFiles/HelloPE.cs.sig
Windows (with jq):
Get-Content ..\testFiles\HelloPE.cs.json `
| jq -r .data `
| openssl base64 -d `
| Set-Content -Encoding Byte ..\testFiles\HelloPE.cs.sig
Windows (with builtin json):
$json = Get-Content ..\testFiles\HelloPE.cs.json -Raw | ConvertFrom-Json
[IO.File]::WriteAllBytes("..\testFiles\HelloPE.cs.sig", [Convert]::FromBase64String($json.data))
Verify the signature with OpenSSL
Linux:
openssl cms -verify \
-in ../testFiles/HelloPE.cs.sig \
-inform DER \
-content ../testFiles/HelloPE.cs \
-CAfile ../signer_certs/PqcCaChain.crt \
-binary \
> /dev/null
CMS Verification successful
Windows:
openssl cms -verify `
-in ..\testFiles\HelloPE.cs.sig `
-inform DER `
-content ..\testFiles\HelloPE.cs `
-CAfile ..\signer_certs\PqcCaChain.crt `
-binary `
> $null
Next steps
You can also explore the PQC Lab PKI 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.