What We’re Testing
QuickZTNA’s SSH Certificate Authority issues real OpenSSH-format certificates (ecdsa-sha2-nistp256-cert-v01@openssh.com) that are compatible with ssh-keygen -L. This is a separate CA from the X.509 CA — it has its own key pair stored in the ssh_ca_public_key and ssh_ca_private_key_encrypted columns of the ca_configs table.
The SSH certificate flow uses POST /api/ssh-certificate with these actions:
-
action: "initialize_ssh_ca"— Generates an ECDSA P-256 key pair for the SSH CA. The public key is stored in OpenSSH format (ecdsa-sha2-nistp256 base64...). Returns the public key and fingerprint with a usage hint to add it tosshd_configasTrustedUserCAKeys. -
action: "issue_ssh_cert"— Issues a user certificate (type 1) signed by the SSH CA. Generates an ephemeral ECDSA P-256 key pair for the certificate. Required fields:- org_id — organization scope
- principal (required) — the SSH username (e.g.,
ubuntu,root) - ttl_hours (optional, default 8) — validity period; must be between 1 and 24 hours
- machine_id (optional) — ties the certificate to a machine; triggers quarantine/posture checks
- extensions (optional) — custom SSH extensions to merge with defaults
Default extensions included:
permit-agent-forwarding,permit-port-forwarding,permit-pty.The key ID is formatted as
quickztna-{org_id}-{principal}-{serial_hex}. Certificates are stored inissued_certificateswithsubject_cnprefixed byssh:(e.g.,ssh:ubuntu). -
action: "list_ssh_certs"— Lists SSH certificates (filtered bysubject_cn LIKE 'ssh:%'). -
action: "revoke_ssh_cert"— Revokes an SSH certificate by ID. -
action: "get_ssh_ca_public_key"— Returns the SSH CA public key for configuringsshd_config. Any org member can call this (not admin-only).
Write operations (initialize_ssh_ca, issue_ssh_cert, revoke_ssh_cert) require org admin role. Read operations (list_ssh_certs, get_ssh_ca_public_key) require org membership.
Your Test Setup
| Machine | Role |
|---|---|
| ⊞ Win-A | Browser for dashboard, PowerShell for API calls |
| 🐧 Linux-C | Target SSH server to test certificate authentication |
Ensure both machines are online and in the same organization. Have your JWT token and org ID ready.
ST1 — Initialize the SSH CA
What it verifies: The initialize_ssh_ca action generates a valid SSH CA key pair and returns the public key in OpenSSH format.
Steps:
- On ⊞ Win-A , run:
curl -s -X POST "https://login.quickztna.com/api/ssh-certificate" `
-H "Authorization: Bearer YOUR_TOKEN" `
-H "Content-Type: application/json" `
-d '{
"action": "initialize_ssh_ca",
"org_id": "YOUR_ORG_ID"
}'
Expected response:
{
"success": true,
"data": {
"ssh_ca_public_key": "ecdsa-sha2-nistp256 AAAAE2VjZH...",
"fingerprint": "AB:CD:12:...",
"message": "SSH CA initialized. Add to sshd_config: TrustedUserCAKeys /path/to/ca.pub"
},
"error": null
}
-
Verify:
ssh_ca_public_keystarts withecdsa-sha2-nistp256fingerprintis a colon-separated uppercase hex stringmessageincludes theTrustedUserCAKeyshint
-
Save the CA public key for later use:
curl -s -X POST "https://login.quickztna.com/api/ssh-certificate" `
-H "Authorization: Bearer YOUR_TOKEN" `
-H "Content-Type: application/json" `
-d '{"action": "initialize_ssh_ca", "org_id": "YOUR_ORG_ID"}' | python -c "import sys,json; print(json.load(sys.stdin)['data']['ssh_ca_public_key'])" > ssh_ca.pub
Pass: Response returns success: true with an OpenSSH-format public key and SHA-256 fingerprint.
Fail / Common issues:
- 403 Forbidden — you must be an org admin to initialize the SSH CA.
- Calling
initialize_ssh_caagain updates the existing CA key pair (does not error). The old SSH certificates will no longer verify against the new CA.
ST2 — Issue an SSH User Certificate
What it verifies: The issue_ssh_cert action returns a valid OpenSSH certificate with correct principal, validity window, and extensions.
Prerequisites: SSH CA must be initialized (ST1 completed).
Steps:
- On ⊞ Win-A , run:
curl -s -X POST "https://login.quickztna.com/api/ssh-certificate" `
-H "Authorization: Bearer YOUR_TOKEN" `
-H "Content-Type: application/json" `
-d '{
"action": "issue_ssh_cert",
"org_id": "YOUR_ORG_ID",
"principal": "ubuntu",
"ttl_hours": 4
}'
Expected response (HTTP 201):
{
"success": true,
"data": {
"certificate_id": "uuid-...",
"certificate": "ecdsa-sha2-nistp256-cert-v01@openssh.com AAAAI...",
"private_key": "-----BEGIN PRIVATE KEY-----\nMIG...\n-----END PRIVATE KEY-----",
"public_key": "ecdsa-sha2-nistp256 AAAAE2VjZH...",
"serial_number": "0000abcdef123456",
"principal": "ubuntu",
"valid_after": "2026-03-17T10:00:00.000Z",
"valid_before": "2026-03-17T14:00:00.000Z",
"ttl_hours": 4,
"fingerprint": "EF:01:23:...",
"usage": "Save certificate as id_ecdsa-cert.pub, private key as id_ecdsa. Use: ssh -i id_ecdsa user@host"
},
"error": null
}
-
Verify:
- HTTP status is 201 (not 200)
certificatestarts withecdsa-sha2-nistp256-cert-v01@openssh.comprivate_keyis a PKCS#8 PEMpublic_keyis in OpenSSH formatprincipalmatches what you requested (ubuntu)ttl_hoursis 4valid_beforeis approximately 4 hours aftervalid_afterusagefield tells you how to save and use the files
-
Save the certificate and key for testing in ST3:
# Save private key
# (copy the private_key value to id_ecdsa)
# Save certificate
# (copy the certificate value to id_ecdsa-cert.pub)
Pass: Response returns HTTP 201 with a valid OpenSSH certificate, private key, and all metadata fields.
Fail / Common issues:
SSH_CA_NOT_INITIALIZED(400) — run ST1 first to initialize the SSH CA.MISSING_FIELDS(400) — theprincipalfield is missing. It is required for SSH certificates.INVALID_TTL(400) —ttl_hoursmust be between 1 and 24 for SSH certificates (stricter than X.509).
ST3 — Verify SSH Certificate on Linux
What it verifies: The issued SSH certificate can be inspected with ssh-keygen -L and contains the correct principal, validity, and extensions.
Prerequisites: Certificate and private key from ST2 saved to files.
Steps:
- On 🐧 Linux-C , save the certificate and key from ST2:
# Save the private key (copy PEM content)
cat > /tmp/id_ecdsa << 'EOF'
-----BEGIN PRIVATE KEY-----
MIG...paste full key here...
-----END PRIVATE KEY-----
EOF
chmod 600 /tmp/id_ecdsa
# Save the certificate (copy the full single-line certificate)
echo "ecdsa-sha2-nistp256-cert-v01@openssh.com AAAAI...paste here..." > /tmp/id_ecdsa-cert.pub
- Inspect the certificate:
ssh-keygen -L -f /tmp/id_ecdsa-cert.pub
Expected output (structure):
/tmp/id_ecdsa-cert.pub:
Type: ecdsa-sha2-nistp256-cert-v01@openssh.com user certificate
Public key: ECDSA-CERT SHA256:...
Signing CA: ECDSA SHA256:...
Key ID: "quickztna-YOUR_ORG_ID-ubuntu-0000abcdef123456"
Serial: 12345678901234
Valid: 2026-03-17T10:00:00 to 2026-03-17T14:00:00
Principals:
ubuntu
Critical Options: (none)
Extensions:
permit-agent-forwarding
permit-port-forwarding
permit-pty
- Verify:
- Type says user certificate
- Key ID contains your org ID, the principal name, and the serial hex
- Serial matches the
serial_numberfrom the API response (as a decimal number) - Principals list contains exactly
ubuntu - Valid range matches
valid_afterandvalid_beforefrom the API response - Extensions include
permit-agent-forwarding,permit-port-forwarding, andpermit-pty - Critical Options is
(none)(no critical options are set by default)
Pass: ssh-keygen -L parses the certificate without errors and shows the correct type, principal, validity, and extensions.
Fail / Common issues:
- “is not a certificate” — the file was not saved correctly. Ensure it is a single line starting with the cert type string.
- Missing principals — the
principalfield was not provided in the API request. - Wrong validity window — check that the system clocks on the API server and test machine are synchronized.
ST4 — Validate TTL Boundaries
What it verifies: The SSH certificate handler enforces the 1—24 hour TTL range and uses the default of 8 hours when not specified.
Steps:
- On ⊞ Win-A , test the default TTL (no
ttl_hoursprovided):
curl -s -X POST "https://login.quickztna.com/api/ssh-certificate" `
-H "Authorization: Bearer YOUR_TOKEN" `
-H "Content-Type: application/json" `
-d '{
"action": "issue_ssh_cert",
"org_id": "YOUR_ORG_ID",
"principal": "test-default-ttl"
}'
Expected: ttl_hours in the response is 8 (the default). The valid_before timestamp is approximately 8 hours after valid_after.
- Test the lower bound (1 hour):
curl -s -X POST "https://login.quickztna.com/api/ssh-certificate" `
-H "Authorization: Bearer YOUR_TOKEN" `
-H "Content-Type: application/json" `
-d '{
"action": "issue_ssh_cert",
"org_id": "YOUR_ORG_ID",
"principal": "test-min-ttl",
"ttl_hours": 1
}'
Expected: success: true, ttl_hours: 1.
- Test exceeding the upper bound (25 hours):
curl -s -X POST "https://login.quickztna.com/api/ssh-certificate" `
-H "Authorization: Bearer YOUR_TOKEN" `
-H "Content-Type: application/json" `
-d '{
"action": "issue_ssh_cert",
"org_id": "YOUR_ORG_ID",
"principal": "test-over-ttl",
"ttl_hours": 25
}'
Expected error response (HTTP 400):
{
"success": false,
"data": null,
"error": {
"code": "INVALID_TTL",
"message": "ttl_hours must be between 1 and 24 for SSH certificates"
}
}
- Test zero TTL:
curl -s -X POST "https://login.quickztna.com/api/ssh-certificate" `
-H "Authorization: Bearer YOUR_TOKEN" `
-H "Content-Type: application/json" `
-d '{
"action": "issue_ssh_cert",
"org_id": "YOUR_ORG_ID",
"principal": "test-zero-ttl",
"ttl_hours": 0
}'
Expected: 400 with INVALID_TTL — zero is below the minimum of 1.
Pass: Default TTL is 8 hours. TTL of 1 succeeds. TTL of 25 and 0 both return INVALID_TTL errors.
Fail / Common issues:
- TTL of 25 is accepted — this is a bug. The handler should reject any value outside 1—24.
- Default TTL is not 8 — check the handler code;
ttl_hours ?? 8provides the fallback.
ST5 — Retrieve SSH CA Public Key and List Certificates
What it verifies: The get_ssh_ca_public_key action returns the CA public key for server configuration, and list_ssh_certs returns only SSH certificates (filtered by ssh: prefix).
Steps:
- On ⊞ Win-A , get the SSH CA public key:
curl -s -X POST "https://login.quickztna.com/api/ssh-certificate" `
-H "Authorization: Bearer YOUR_TOKEN" `
-H "Content-Type: application/json" `
-d '{
"action": "get_ssh_ca_public_key",
"org_id": "YOUR_ORG_ID"
}'
Expected response:
{
"success": true,
"data": {
"ssh_ca_public_key": "ecdsa-sha2-nistp256 AAAAE2VjZH...",
"fingerprint": "AB:CD:12:...",
"usage": "Add to /etc/ssh/sshd_config: TrustedUserCAKeys /path/to/ca.pub"
},
"error": null
}
This action only requires org membership (not admin), so any authenticated user in the org can retrieve it.
- On 🐧 Linux-C , you would use this key in
sshd_config:
# Save the CA public key
echo "ecdsa-sha2-nistp256 AAAAE2VjZH..." > /etc/ssh/ca.pub
# Add to sshd_config
echo "TrustedUserCAKeys /etc/ssh/ca.pub" >> /etc/ssh/sshd_config
# Reload sshd
systemctl reload sshd
(This step is informational — only do this on a test machine you control.)
- List all SSH certificates:
curl -s -X POST "https://login.quickztna.com/api/ssh-certificate" `
-H "Authorization: Bearer YOUR_TOKEN" `
-H "Content-Type: application/json" `
-d '{
"action": "list_ssh_certs",
"org_id": "YOUR_ORG_ID"
}'
Expected response:
{
"success": true,
"data": {
"certificates": [
{
"id": "uuid-...",
"org_id": "YOUR_ORG_ID",
"machine_id": null,
"serial_number": "0000abcdef123456",
"subject_cn": "ssh:ubuntu",
"expires_at": "2026-03-17T14:00:00.000Z",
"fingerprint": "EF:01:23:...",
"revoked": false,
"revoked_at": null,
"issued_at": "2026-03-17T10:00:00.000Z"
}
]
},
"error": null
}
- Verify:
- Only SSH certificates appear (all
subject_cnvalues start withssh:) - X.509 certificates from
POST /api/issue-certificatedo not appear in this list - Certificates from ST2 and ST4 are present
- The list is ordered by
issued_at DESC(newest first), limit 100
- Only SSH certificates appear (all
Pass: CA public key is returned in OpenSSH format. The SSH certificate list shows only SSH-type certificates with ssh: prefix in subject_cn.
Fail / Common issues:
SSH_CA_NOT_INITIALIZED(400) — the SSH CA was never initialized. Run ST1 first.- Empty certificate list — verify you are using the correct
org_idwhere SSH certificates were issued. - X.509 certificates appearing in the list — the SQL filter
subject_cn LIKE 'ssh:%'is not working correctly.
Summary
| Sub-test | What it proves |
|---|---|
| ST1 | SSH CA initialization generates an ECDSA P-256 key pair in OpenSSH format |
| ST2 | SSH user certificates are issued with correct principal, serial, validity, and extensions (HTTP 201) |
| ST3 | Issued certificates are parseable by ssh-keygen -L with correct type, key ID, and principals |
| ST4 | TTL enforcement rejects values outside 1—24 hours and defaults to 8 hours |
| ST5 | CA public key retrieval works for non-admin members; list returns only SSH-type certificates |