Profiles
Profiles
Section titled “Profiles”Profiles are YAML templates that define certificate characteristics: algorithm, validity, subject DN, extensions, and more. Each profile produces exactly one certificate type.
1. What is a Profile?
Section titled “1. What is a Profile?”A profile is a policy template stored as a YAML file that determines:
- Algorithm: The cryptographic algorithm for this certificate
- Mode: How multiple algorithms are combined (simple, catalyst, composite)
- Validity period: How long the certificate remains valid
- Extensions: X.509 extensions configuration
Design Principle: 1 Profile = 1 Certificate
Section titled “Design Principle: 1 Profile = 1 Certificate”Each profile produces exactly one certificate. To create multiple certificates (e.g., signature + encryption), use multiple profiles.
1.1 Profile Categories
Section titled “1.1 Profile Categories”Profiles are organized by category and stored in profiles/:
ec/- ECDSA-based profiles (modern classical)rsa/- RSA-based profiles (legacy compatibility)rsa-pss/- RSA-PSS profilesml/- ML-DSA and ML-KEM profiles (post-quantum)slh/- SLH-DSA profiles (hash-based post-quantum)hybrid/catalyst/- Catalyst hybrid profiles (ITU-T X.509 Section 9.8)hybrid/composite/- IETF composite hybrid profiles
1.2 Profile Modes
Section titled “1.2 Profile Modes”| Mode | Description | Algorithm(s) |
|---|---|---|
simple | Single algorithm | 1 |
catalyst | Dual-key certificate (ITU-T X.509 9.8) | 2 |
composite | IETF composite signature format | 2 |
Simple Mode
Section titled “Simple Mode”Standard X.509 certificate with a single algorithm:
name: ec/tls-serverdescription: "TLS server ECDSA P-256"
algorithm: ecdsa-p256validity: 365d
extensions: keyUsage: critical: true values: - digitalSignature extKeyUsage: values: - serverAuthCatalyst Mode (ITU-T X.509 Section 9.8)
Section titled “Catalyst Mode (ITU-T X.509 Section 9.8)”A single certificate containing both classical and PQC public keys:
name: hybrid/catalyst/tls-serverdescription: "TLS server hybrid ECDSA P-256 + ML-DSA-65"
mode: catalystalgorithms: - ecdsa-p256 # Classical algorithm (first) - ml-dsa-65 # PQC algorithm (second)validity: 365d
extensions: keyUsage: critical: true values: - digitalSignature extKeyUsage: values: - serverAuthComposite Mode (IETF Format)
Section titled “Composite Mode (IETF Format)”IETF composite signature where both signatures are combined and must validate:
name: hybrid/composite/tls-serverdescription: "TLS server hybrid composite ECDSA P-256 + ML-DSA-65"
mode: compositealgorithms: - ecdsa-p256 # Classical algorithm (first) - ml-dsa-65 # PQC algorithm (second)validity: 365d
extensions: keyUsage: critical: true values: - digitalSignature extKeyUsage: values: - serverAuth2. Builtin Profiles
Section titled “2. Builtin Profiles”EC (ECDSA - Modern Classical)
Section titled “EC (ECDSA - Modern Classical)”| Name | Algorithm | Use Case |
|---|---|---|
ec/root-ca | ECDSA P-384 | Root CA |
ec/issuing-ca | ECDSA P-256 | Intermediate CA |
ec/tls-server | ECDSA P-256 | TLS server |
ec/tls-client | ECDSA P-256 | TLS client |
ec/email | ECDSA P-256 | S/MIME email |
ec/code-signing | ECDSA P-256 | Code signing |
ec/timestamping | ECDSA P-256 | RFC 3161 TSA |
ec/ocsp-responder | ECDSA P-384 | OCSP responder |
ec/signing | ECDSA P-256 | Document signing |
RSA (Legacy Compatibility)
Section titled “RSA (Legacy Compatibility)”| Name | Algorithm | Use Case |
|---|---|---|
rsa/root-ca | RSA 4096 | Root CA |
rsa/issuing-ca | RSA 4096 | Intermediate CA |
rsa/tls-server | RSA 2048 | TLS server |
rsa/tls-client | RSA 2048 | TLS client |
rsa/email | RSA 2048 | S/MIME email |
rsa/code-signing | RSA 2048 | Code signing |
rsa/timestamping | RSA 2048 | RFC 3161 TSA |
rsa/signing | RSA 2048 | Document signing |
rsa/encryption | RSA 2048 | Data encryption |
RSA-PSS
Section titled “RSA-PSS”| Name | Algorithm | Use Case |
|---|---|---|
rsa-pss/tls-server | RSA 4096 | TLS server (TLS 1.3) |
ML-DSA-KEM (Post-Quantum)
Section titled “ML-DSA-KEM (Post-Quantum)”| Name | Algorithm | Use Case |
|---|---|---|
ml/root-ca | ML-DSA-87 | Root CA |
ml/issuing-ca | ML-DSA-65 | Intermediate CA |
ml/tls-server-sign | ML-DSA-65 | TLS server signature |
ml/tls-server-encrypt | ML-KEM-768 | TLS server encryption |
ml/tls-client | ML-DSA-65 | TLS client |
ml/email-sign | ML-DSA-65 | S/MIME signature |
ml/email-encrypt | ML-KEM-768 | S/MIME encryption |
ml/code-signing | ML-DSA-65 | Code signing |
ml/timestamping | ML-DSA-65 | RFC 3161 TSA |
ml/ocsp-responder | ML-DSA-65 | OCSP responder |
ml/signing | ML-DSA-65 | Document signing |
ml/encryption | ML-KEM-768 | Data encryption |
SLH-DSA (Hash-Based Post-Quantum)
Section titled “SLH-DSA (Hash-Based Post-Quantum)”| Name | Algorithm | Use Case |
|---|---|---|
slh/root-ca | SLH-DSA-256f | Root CA |
slh/issuing-ca | SLH-DSA-192f | Intermediate CA |
slh/tls-server | SLH-DSA-128f | TLS server |
slh/tls-client | SLH-DSA-128f | TLS client |
slh/timestamping | SLH-DSA-256s | RFC 3161 TSA |
slh/signing | SLH-DSA-256s | Document signing |
slh/ocsp-responder | SLH-DSA-256s | OCSP responder |
Hybrid Catalyst (ITU-T X.509 Section 9.8)
Section titled “Hybrid Catalyst (ITU-T X.509 Section 9.8)”| Name | Algorithms | Use Case |
|---|---|---|
hybrid/catalyst/root-ca | ECDSA P-384 + ML-DSA-87 | Root CA |
hybrid/catalyst/issuing-ca | ECDSA P-256 + ML-DSA-65 | Intermediate CA |
hybrid/catalyst/tls-server | ECDSA P-256 + ML-DSA-65 | TLS server |
hybrid/catalyst/tls-client | ECDSA P-256 + ML-DSA-65 | TLS client |
hybrid/catalyst/timestamping | ECDSA P-384 + ML-DSA-65 | RFC 3161 TSA |
hybrid/catalyst/ocsp-responder | ECDSA P-384 + ML-DSA-65 | OCSP responder |
hybrid/catalyst/signing | ECDSA P-256 + ML-DSA-65 | Document signing |
Hybrid Composite (IETF Format)
Section titled “Hybrid Composite (IETF Format)”| Name | Algorithms | Use Case |
|---|---|---|
hybrid/composite/root-ca | ECDSA P-384 + ML-DSA-87 | Root CA |
hybrid/composite/issuing-ca | ECDSA P-256 + ML-DSA-65 | Intermediate CA |
hybrid/composite/tls-server | ECDSA P-256 + ML-DSA-65 | TLS server |
hybrid/composite/tls-client | ECDSA P-256 + ML-DSA-65 | TLS client |
hybrid/composite/timestamping | ECDSA P-384 + ML-DSA-65 | RFC 3161 TSA |
hybrid/composite/signing | ECDSA P-384 + ML-DSA-87 | Document signing |
hybrid/composite/ocsp-responder | ECDSA P-384 + ML-DSA-87 | OCSP responder |
eIDAS Qualified Certificates
Section titled “eIDAS Qualified Certificates”| Name | Algorithm | Use Case |
|---|---|---|
eidas/qc-esign | ECDSA P-256 | Qualified electronic signature (natural person) |
eidas/qc-eseal | ECDSA P-256 | Qualified electronic seal (legal person) |
eidas/qc-web | ECDSA P-256 | Qualified Website Authentication Certificate (QWAC) |
eidas/qc-tsa | ECDSA P-256 | Qualified Timestamping Authority |
These profiles include QCStatements extension for eIDAS compliance (EU 910/2014).
Note: For qualified timestamping, when a TSA certificate has
qcCompliance, timestamp tokens automatically include theesi4-qtstStatement-1extension per ETSI EN 319 422. See TSA.md.
3. CLI Commands
Section titled “3. CLI Commands”List Available Profiles
Section titled “List Available Profiles”qpki profile listView Profile Details
Section titled “View Profile Details”qpki profile info hybrid/catalyst/tls-serverShow Profile YAML
Section titled “Show Profile YAML”qpki profile show ec/root-caExport Profile for Customization
Section titled “Export Profile for Customization”# Export single profileqpki profile export ec/tls-server ./my-tls-server.yaml
qpki profile export --all ./templates/Validate a Custom Profile
Section titled “Validate a Custom Profile”qpki profile validate my-profile.yaml4. Creating Custom Profiles
Section titled “4. Creating Custom Profiles”Export a builtin profile, modify it, and use it:
# Export a templateqpki profile export ec/tls-server ./my-custom.yaml
vim ./my-custom.yaml
qpki credential enroll --profile ./my-custom.yaml \ --var cn=server.example.com --var dns_names=server.example.com --ca-dir ./ca
qpki cert issue --profile ./my-custom.yaml --csr server.csr --out server.crt --ca-dir ./caProfile Loading Priority
Section titled “Profile Loading Priority”QPKI uses a two-tier profile system:
- Built-in profiles - Embedded in the binary (default)
- Custom profiles - Loaded from the CA’s
profiles/directory
Custom profiles can be used in two ways:
- Override: Use the same name as a built-in profile to replace it entirely
- New profile: Use a different name to add a new profile alongside built-ins
To override a built-in profile:
# Export the built-in profileqpki profile export ec/tls-server ./tls-server.yaml
vim ./tls-server.yaml
mkdir -p ./ca/profiles/eccp ./tls-server.yaml ./ca/profiles/ec/tls-server.yaml
qpki credential enroll --profile ec/tls-server --var cn=server.example.com --ca-dir ./ca --cred-dir ./credentialsTo check which version is active, use qpki profile list:
qpki profile list --dir ./caThe SOURCE column indicates:
default- Built-in profilecustom (overrides default)- Custom profile overriding a built-incustom- Custom profile with no built-in equivalent
To revert to the built-in version, simply delete the custom profile file from CA/profiles/.
5. YAML Schema
Section titled “5. YAML Schema”# =============================================================================# =============================================================================
name: string # Profile identifierdescription: string # Human-readable description
# Algorithm - Simple profile (single algorithm)algorithm: string # e.g., ecdsa-p256, rsa-4096, ml-dsa-65
# Algorithm - Hybrid profile (two algorithms)mode: string # catalyst | compositealgorithms: # List of algorithm IDs - ecdsa-p256 # Classical algorithm (first) - ml-dsa-65 # PQC algorithm (second)
# Signature - Override signature algorithm defaultssignature: scheme: string # ecdsa | pkcs1v15 | rsassa-pss | ed25519 hash: string # sha256 | sha384 | sha512 | sha3-256 | sha3-384 | sha3-512 pss: # RSA-PSS specific parameters salt_length: int # Salt length in bytes (-1 = hash length) mgf: string # MGF hash algorithm (defaults to signature hash)
# Validity - fixed value or templatevalidity: duration # Duration format (e.g., 365d, 8760h, 1y) # Or template: "{{ validity }}" (resolved at enrollment)
# Variables - Input parameters with validationvariables: <name>: type: string|integer|list|dns_name|dns_names|ip_list|email|uri|oid|duration required: bool default: value description: string # Type-specific constraints...
# Subject DN - Certificate subject fieldssubject: cn: "{{ variable }}" # Common Name o: "{{ variable }}" # Organization ou: "static value" # Organizational Unit (can be static) c: "{{ variable }}" # Country
# Extensions - X.509 v3 extensionsextensions: basicConstraints: critical: bool # MUST true for CA (RFC 5280) ca: bool # true=CA, false=end-entity pathLen: int # Max sub-CAs (only if ca=true) keyUsage: critical: bool values: [digitalSignature, keyEncipherment, ...] extKeyUsage: values: [serverAuth, clientAuth, ...] subjectAltName: dns: "{{ dns_names }}" # DNS names from variable ip: "{{ ip_addresses }}" # IP addresses from variable dns_include_cn: bool # Auto-add CN to DNS SANs certificatePolicies: policies: - oid: string cps: string crlDistributionPoints: urls: [string, ...] authorityInfoAccess: caIssuers: [string, ...] ocsp: [string, ...]Template Variable Substitution
Section titled “Template Variable Substitution”Variables are referenced using {{ variable_name }} syntax. Supported locations:
| Location | Supported | Example |
|---|---|---|
subject: fields | ✅ Yes | cn: "{{ cn }}" |
subjectAltName.dns | ✅ Yes | dns: "{{ dns_names }}" |
subjectAltName.ip | ✅ Yes | ip: "{{ ip_addresses }}" |
subjectAltName.email | ✅ Yes | email: "{{ emails }}" |
validity: | ✅ Yes | validity: "{{ validity }}" |
crlDistributionPoints.urls | ✅ Yes | urls: ["{{ crl_url }}"] |
authorityInfoAccess.caIssuers | ✅ Yes | caIssuers: ["{{ ca_issuer }}"] |
authorityInfoAccess.ocsp | ✅ Yes | ocsp: ["{{ ocsp_url }}"] |
certificatePolicies.cps | ✅ Yes | cps: "{{ cps_url }}" |
Template variables are resolved at enrollment time. Use duration type for validity and uri type for URLs.
DN Encoding (RFC 5280)
Section titled “DN Encoding (RFC 5280)”By default, DN attributes use UTF8String (ASN.1 tag 12). You can specify encoding per attribute:
subject: cn: "{{ cn }}" # UTF8String (default) o: value: "ACME Corp" encoding: printable # PrintableString (tag 19) c: value: "FR" encoding: printable # Required by RFC 5280 email: value: "{{ email }}" encoding: ia5 # Required by RFC 5280Available encodings:
| Encoding | ASN.1 Tag | Characters | Use Case |
|---|---|---|---|
utf8 | 12 | Full Unicode | Default, RFC 5280 recommended |
printable | 19 | A-Za-z0-9 ’()+,-./:=? space | Country (C), legacy |
ia5 | 22 | ASCII 7-bit | Email addresses |
RFC 5280 constraints (auto-applied):
c(country): automatically usesprintableencodingemail: automatically usesia5encoding
You can omit the encoding for these attributes - it will be applied automatically.
If you explicitly specify a wrong encoding (e.g., c: { encoding: utf8 }), a validation error is returned.
6. Declarative Variables
Section titled “6. Declarative Variables”Profiles can declare typed variables with validation constraints. Variables enable:
- Input validation before certificate issuance
- Pattern matching (regex)
- Enumerated values
- Domain constraints (allowed_suffixes, allowed_ranges)
- Default values
Variable Types
Section titled “Variable Types”| Type | Go Type | Description |
|---|---|---|
string | string | Text with optional pattern/enum validation |
integer | int | Number with optional min/max validation |
boolean | bool | True/false value |
list | []string | List of strings with suffix/prefix constraints |
ip_list | []string | List of IP addresses with CIDR range constraints |
dns_name | string | Single DNS name with RFC 1035/1123 validation + wildcard policy |
dns_names | []string | List of DNS names with RFC 1035/1123 validation + wildcard policy |
email | string | Email address with RFC 5322 validation |
uri | string | URI with RFC 3986 validation + scheme/host constraints |
oid | string | Object Identifier in dot-notation (e.g., 1.2.3.4) |
duration | string | Duration string (Go format + d/w/y units) |
Profile with Variables Example
Section titled “Profile with Variables Example”name: ec/tls-server-securedescription: "Production TLS server with validation"
algorithm: ecdsa-p256validity: "{{ validity }}" # Template - resolved at enrollment
variables: cn: type: string required: true pattern: "^[a-zA-Z0-9][a-zA-Z0-9.-]+$" description: "Common Name (FQDN)"
organization: type: string required: false default: "ACME Corp" description: "Organization name"
country: type: string required: false default: "FR" pattern: "^[A-Z]{2}$" minLength: 2 maxLength: 2 description: "ISO 3166-1 alpha-2 country code"
environment: type: string required: false default: "production" enum: ["development", "staging", "production"] description: "Deployment environment"
dns_names: type: list required: false default: [] constraints: allowed_suffixes: - ".example.com" - ".internal" denied_prefixes: - "test-" max_items: 10 description: "DNS Subject Alternative Names"
ip_addresses: type: ip_list required: false constraints: allowed_ranges: - "10.0.0.0/8" - "192.168.0.0/16" max_items: 5 description: "IP Subject Alternative Names"
validity: type: duration required: false default: "365d" min_duration: "1d" max_duration: "825d" description: "Certificate validity period"
crl_url: type: uri required: false constraints: allowed_schemes: ["http", "https"] description: "CRL distribution point URL"
ocsp_url: type: uri required: false constraints: allowed_schemes: ["http", "https"] description: "OCSP responder URL"
subject: cn: "{{ cn }}" o: "{{ organization }}" c: "{{ country }}"
extensions: basicConstraints: critical: true ca: false keyUsage: critical: true values: - digitalSignature - keyEncipherment extKeyUsage: values: - serverAuth # SANs with variable substitution subjectAltName: dns: "{{ dns_names }}" ip: "{{ ip_addresses }}" dns_include_cn: true # CDP/AIA with template variables crlDistributionPoints: urls: - "{{ crl_url }}" authorityInfoAccess: ocsp: - "{{ ocsp_url }}"Variable Constraints Reference
Section titled “Variable Constraints Reference”String Constraints
Section titled “String Constraints”variables: my_var: type: string required: true # Must be provided default: "value" # Default if not provided pattern: "^[a-z]+$" # Regex pattern enum: ["a", "b", "c"] # Allowed values minLength: 1 # Minimum length maxLength: 64 # Maximum lengthInteger Constraints
Section titled “Integer Constraints”variables: days: type: integer required: false default: 365 min: 1 # Minimum value max: 825 # Maximum value enum: ["30", "90", "365"] # Allowed values (as strings)List Constraints
Section titled “List Constraints”variables: dns_names: type: list default: [] constraints: allowed_suffixes: # Each item must end with one of these - ".example.com" denied_prefixes: # Items starting with these are rejected - "internal-" min_items: 1 # Minimum number of items max_items: 10 # Maximum number of itemsIP List Constraints
Section titled “IP List Constraints”variables: ip_addresses: type: ip_list constraints: allowed_ranges: # IPs must be within one of these CIDRs - "10.0.0.0/8" - "192.168.0.0/16" max_items: 5Email Type (RFC 5322)
Section titled “Email Type (RFC 5322)”The email type validates email addresses according to RFC 5322 using Go’s net/mail package.
Normalization (automatic):
- Lowercase:
User@Example.COM→user@example.com(RFC 5321 recommendation)
Constraints:
variables: email: type: email required: true constraints: allowed_suffixes: # Domain must match one of these - "@example.com" - "@acme.com" denied_prefixes: # Local part must not start with these - "admin" - "root"Example validation:
| Value | Options | Result |
|---|---|---|
user@example.com | default | 🟢 Valid |
User@Example.COM | default | 🟢 Normalized to lowercase |
user+tag@example.com | default | 🟢 Plus addressing valid |
admin@example.com | denied_prefixes: [admin] | 🔴 Denied prefix |
user@other.com | allowed_suffixes: [@example.com] | 🔴 Domain not allowed |
not-an-email | default | 🔴 Invalid format |
Use case: S/MIME certificates, TLS client authentication with email identity.
# Example: Email certificate for S/MIMEvariables: email: type: email required: true constraints: allowed_suffixes: - "@acme.com" - "@acme.fr" description: "User email address (must be @acme.com or @acme.fr)"URI Type (RFC 3986)
Section titled “URI Type (RFC 3986)”The uri type validates URIs according to RFC 3986 and supports scheme/host constraints.
Normalization (automatic):
- Scheme lowercase:
HTTP://example.com→http://example.com
Constraints:
variables: ocsp_url: type: uri required: false default: "http://ocsp.example.com" constraints: allowed_schemes: # Scheme must be one of these - "http" - "https" allowed_hosts: # Host must be one of these - "ocsp.example.com" - "ocsp2.example.com"Example validation:
| Value | Options | Result |
|---|---|---|
http://example.com | default | 🟢 Valid |
https://example.com/path | default | 🟢 Valid with path |
HTTP://Example.COM | default | 🟢 Scheme normalized |
ftp://example.com | allowed_schemes: [http, https] | 🔴 Scheme not allowed |
http://other.com | allowed_hosts: [example.com] | 🔴 Host not allowed |
example.com | default | 🔴 Missing scheme |
Use case: AIA (Authority Information Access) URLs, CRL Distribution Points, OCSP responder URLs.
# Example: AIA configurationvariables: ocsp_url: type: uri constraints: allowed_schemes: ["http", "https"] allowed_hosts: ["ocsp.example.com"] description: "OCSP responder URL"
ca_issuer_url: type: uri constraints: allowed_schemes: ["http", "https"] description: "CA certificate URL"OID Type (Object Identifier)
Section titled “OID Type (Object Identifier)”The oid type validates Object Identifiers in dot-notation format (e.g., 1.2.840.113549.1.1.11).
Validation rules:
- Format: digits separated by dots (e.g.,
1.2.3.4) - Minimum 2 arcs required (e.g.,
1.2) - First arc must be 0, 1, or 2
- Second arc must be < 40 when first arc is 0 or 1
Constraints:
variables: policy_oid: type: oid required: false default: "1.3.6.1.4.1.99999.1" constraints: allowed_suffixes: # OID must start with one of these (prefix check) - "2.16.840.1.101.3.4" # NIST algorithms arc - "1.3.6.1.4.1" # Private enterprise arcNote: For OID type,
allowed_suffixesacts as allowed prefixes - the OID must start with one of the specified values.
Example validation:
| Value | Options | Result |
|---|---|---|
1.2.3 | default | 🟢 Valid |
2.16.840.1.101.3.4.3.17 | default | 🟢 ML-DSA-44 OID |
0.2.3 | default | 🟢 First arc 0 |
3.2.3 | default | 🔴 First arc > 2 |
0.40.1 | default | 🔴 Second arc >= 40 under arc 0 |
1 | default | 🔴 Single arc |
1.a.3 | default | 🔴 Non-numeric |
Use case: Certificate policies, custom extension OIDs, algorithm identifiers.
# Example: Certificate policyvariables: policy_oid: type: oid default: "1.3.6.1.4.1.99999.1.1" constraints: allowed_suffixes: - "1.3.6.1.4.1.99999" # Your private enterprise arc description: "Certificate policy OID"Duration Type
Section titled “Duration Type”The duration type validates duration strings, supporting both Go’s standard format and extended units for days, weeks, and years.
Supported formats:
- Go standard:
1h,30m,60s,1h30m - Extended:
1d(days),1w(weeks),1y(years) - Combined:
1y6m,30d12h,1w1d
Conversion:
- 1 day = 24 hours
- 1 week = 7 days
- 1 year = 365 days
Constraints:
variables: validity: type: duration required: false default: "365d" min_duration: "1d" # Minimum duration max_duration: "825d" # Maximum (CA/B Forum limit)Example validation:
| Value | Options | Result |
|---|---|---|
365d | default | 🟢 Valid |
1y | default | 🟢 365 days |
2w | default | 🟢 14 days |
30d12h | default | 🟢 Combined |
1h30m | default | 🟢 Go format |
12h | min_duration: "1d" | 🔴 Below minimum |
3y | max_duration: "825d" | 🔴 Above maximum |
abc | default | 🔴 Invalid format |
Use case: Certificate validity periods, CRL update intervals.
# Example: Validity with CA/B Forum constraintsvariables: validity: type: duration default: "365d" min_duration: "1d" max_duration: "825d" # CA/B Forum max for TLS description: "Certificate validity period"
crl_validity: type: duration default: "7d" min_duration: "1h" max_duration: "30d" description: "CRL validity period"DNS Name Type (RFC 1035/1123)
Section titled “DNS Name Type (RFC 1035/1123)”The dns_name and dns_names types provide built-in DNS name validation according to RFC 1035/1123, plus optional wildcard policy (RFC 6125).
Normalization (automatic):
- Lowercase:
API.Example.COM→api.example.com(RFC 4343) - Trailing dot stripped:
example.com.→example.com(FQDN)
Validation rules:
- Total DNS name length ≤ 253 characters
- Each label (between dots) ≤ 63 characters
- No empty labels (double dots
..rejected) - Labels contain only alphanumeric characters and hyphens
- Labels don’t start or end with a hyphen
- Minimum 2 labels required (unless
allow_single_label: true)
variables: # Single DNS name (e.g., for CN) cn: type: dns_name required: true wildcard: # Wildcard policy (optional) allowed: true # Permit wildcards like *.example.com (default: false) single_label: true # RFC 6125: * matches exactly one label (default: true) forbid_public_suffix: true # Block wildcards on public suffixes like *.co.uk
# Internal hostname (single label allowed) internal_host: type: dns_name allow_single_label: true # Permit "localhost", "db-master", etc.
# List of DNS names (e.g., for SANs) dns_names: type: dns_names wildcard: allowed: false # No wildcards in SANs constraints: allowed_suffixes: # Domain restrictions (label boundary check) - ".example.com" max_items: 10DNS Name Options:
| Option | Default | Description |
|---|---|---|
allow_single_label | false | Permit single-label names like localhost |
Wildcard Policy (RFC 6125):
| Option | Default | Description |
|---|---|---|
allowed | false | Whether wildcards are permitted |
single_label | true | RFC 6125: * matches exactly one DNS label |
forbid_public_suffix | false | Block wildcards on public suffixes (*.co.uk, *.com.au) |
Wildcard validation rules:
- Wildcard must be leftmost label:
*.example.com🟢,api.*.com🔴 - Minimum 3 labels required:
*.example.com🟢,*.com🔴 - Only one wildcard allowed:
*.*.example.com🔴 - With
forbid_public_suffix: true:*.co.uk🔴,*.example.co.uk🟢
Suffix matching (security):
The allowed_suffixes constraint uses label boundary matching to prevent security issues:
| DNS Name | Suffix | Result | Reason |
|---|---|---|---|
api.example.com | .example.com | 🟢 | Matches on label boundary |
fakeexample.com | .example.com | 🔴 | Not on label boundary |
example.com | .example.com | 🟢 | Exact match |
Example validation:
| Value | Options | Result |
|---|---|---|
api.example.com | default | 🟢 Valid DNS name |
API.Example.COM | default | 🟢 Normalized to lowercase |
example.com. | default | 🟢 Trailing dot stripped |
*.example.com | allowed: true | 🟢 Valid wildcard |
*.example.com | allowed: false | 🔴 Wildcards not allowed |
*.co.uk | forbid_public_suffix: true | 🔴 Public suffix blocked |
localhost | default | 🔴 Single label (needs 2+) |
localhost | allow_single_label: true | 🟢 Single label allowed |
*.com | allowed: true | 🔴 Too few labels |
example..com | any | 🔴 Empty label (double dot) |
When to use dns_name vs string:
Use dns_name when:
- You want automatic DNS format validation
- You need wildcard certificate support with proper RFC 6125 enforcement
- You want case normalization and trailing dot handling
Use string with pattern when:
- You need custom regex validation
- You have non-standard hostname requirements
# Preferred: Built-in DNS validationcn: type: dns_name wildcard: allowed: true forbid_public_suffix: true # Recommended for production
internal_cn: type: dns_name allow_single_label: true
cn: type: string pattern: "^[a-z0-9][a-z0-9.-]+$"Using Variables via CLI
Section titled “Using Variables via CLI”Using —var flags
Section titled “Using —var flags”# Single variableqpki credential enroll --profile ec/tls-server-secure \ --var cn=api.example.com
qpki credential enroll --profile ec/tls-server-secure \ --var cn=api.example.com \ --var dns_names=api.example.com,api2.example.com \ --var environment=production \ --var organization="My Company"
qpki credential enroll --profile ec/tls-server-secure \ --var cn=api.example.com \ --var ip_addresses=10.0.0.1,10.0.0.2Using —var-file
Section titled “Using —var-file”Create a YAML file with variable values:
cn: api.example.comorganization: "My Company"country: USenvironment: productiondns_names: - api.example.com - api2.example.comip_addresses: - 10.0.0.1 - 10.0.0.2validity: "365d"Then use it:
qpki credential enroll --profile ec/tls-server-secure --var-file vars.yamlMixing —var-file and —var
Section titled “Mixing —var-file and —var”File values are loaded first, then —var flags override:
# Load defaults from file, override CNqpki credential enroll --profile ec/tls-server-secure \ --var-file defaults.yaml \ --var cn=custom.example.comVariable Precedence
Section titled “Variable Precedence”When using profiles with variables, the CLI automatically:
- Loads variables from
--var-file(if provided) - Overrides with
--varflags - Validates all values against profile constraints
- Applies default values for missing optional variables
- Builds subject DN from resolved variables
# Load defaults from file, override specific valuesqpki credential enroll --profile ec/tls-server-secure \ --var-file defaults.yaml \ --var cn=custom.example.comError Messages
Section titled “Error Messages”Variable validation provides clear error messages:
# Pattern mismatchvariable validation failed: cn: value "-invalid" does not match pattern "^[a-zA-Z0-9][a-zA-Z0-9.-]+$"
variable validation failed: environment: value "test" not in allowed values [development staging production]
variable validation failed: validity: duration "1000d" exceeds maximum "825d"
variable validation failed: dns_names: "api.other.com" does not match allowed suffixes [.example.com .internal]
variable validation failed: ip_addresses: IP "8.8.8.8" not in allowed ranges [10.0.0.0/8 192.168.0.0/16]7. X.509 Extensions
Section titled “7. X.509 Extensions”Supported Extensions
Section titled “Supported Extensions”| Extension | OID | Default Critical | Description |
|---|---|---|---|
keyUsage | 2.5.29.15 | true | Key usage restrictions (RFC 5280 §4.2.1.3) |
extKeyUsage | 2.5.29.37 | false | Extended key usage purposes (RFC 5280 §4.2.1.12) |
basicConstraints | 2.5.29.19 | true | CA flag and path length (RFC 5280 §4.2.1.9) |
subjectAltName | 2.5.29.17 | false | Alternative identities (RFC 5280 §4.2.1.6) |
crlDistributionPoints | 2.5.29.31 | false | CRL locations (RFC 5280 §4.2.1.13) |
authorityInfoAccess | 1.3.6.1.5.5.7.1.1 | false | OCSP and CA issuer URLs (RFC 5280 §4.2.2.1) |
certificatePolicies | 2.5.29.32 | false | Certificate policies (RFC 5280 §4.2.1.4) |
nameConstraints | 2.5.29.30 | true | Name restrictions for CA (RFC 5280 §4.2.1.10) |
ocspNoCheck | 1.3.6.1.5.5.7.48.1.5 | false | Skip OCSP check for responder (RFC 6960 §4.2.2.2.1) |
qcStatements | 1.3.6.1.5.5.7.1.3 | false | Qualified Certificate statements (ETSI EN 319 412-5) |
Automatic Extensions (Not Configurable)
Section titled “Automatic Extensions (Not Configurable)”These extensions are automatically generated by QPKI and cannot be configured in profiles:
| Extension | OID | Critical | Description |
|---|---|---|---|
| Subject Key Identifier | 2.5.29.14 | false | SHA-1 hash of public key (RFC 5280 §4.2.1.2) |
| Authority Key Identifier | 2.5.29.35 | false | Copied from issuer’s SKI (RFC 5280 §4.2.1.1) |
- SKI: Computed as
SHA-1(SubjectPublicKeyInfo)per RFC 5280 method 1 - AKI: Copied from the issuing CA certificate’s SKI
Extensions Configuration Example
Section titled “Extensions Configuration Example”extensions: keyUsage: critical: true values: - digitalSignature - keyEncipherment
extKeyUsage: critical: false values: - serverAuth - clientAuth
basicConstraints: critical: true ca: false
# CRL Distribution Points - static or template crlDistributionPoints: urls: - "http://pki.example.com/crl/ca.crl" # Static URL - "{{ crl_url }}" # Or template variable
# Authority Info Access - static or template authorityInfoAccess: ocsp: - "{{ ocsp_url }}" # Template variable caIssuers: - "{{ ca_issuer }}" # Template variable
# Certificate Policies - CPS can be template certificatePolicies: policies: - oid: "2.23.140.1.2.1" cps: "{{ cps_url }}" # Template variable
# Subject Alternative Names - template variables subjectAltName: dns: "{{ dns_names }}" # Template variable (expanded at runtime) email: "{{ email }}" # Template variable ip: "{{ ip_addresses }}" # Template variable dns_include_cn: true # Auto-add CN to DNS SANsKey Usage Values
Section titled “Key Usage Values”| Value | Description |
|---|---|
digitalSignature | Verify digital signatures |
contentCommitment | Non-repudiation |
keyEncipherment | Encrypt keys (RSA key transport) |
dataEncipherment | Encrypt data directly |
keyAgreement | Key agreement (ECDH) |
keyCertSign | Sign certificates (CA only) |
crlSign | Sign CRLs (CA only) |
encipherOnly | Encipher only (with keyAgreement) |
decipherOnly | Decipher only (with keyAgreement) |
Extended Key Usage Values
Section titled “Extended Key Usage Values”| Value | Description | OID |
|---|---|---|
serverAuth | TLS server authentication | 1.3.6.1.5.5.7.3.1 |
clientAuth | TLS client authentication | 1.3.6.1.5.5.7.3.2 |
codeSigning | Code signing | 1.3.6.1.5.5.7.3.3 |
emailProtection | S/MIME email | 1.3.6.1.5.5.7.3.4 |
timeStamping | Trusted timestamping | 1.3.6.1.5.5.7.3.8 |
ocspSigning | OCSP responder signing | 1.3.6.1.5.5.7.3.9 |
any | Any extended key usage | 2.5.29.37.0 |
Custom OIDs
Section titled “Custom OIDs”In addition to predefined values, you can specify custom OIDs directly in the values list using dot notation:
extKeyUsage: values: - serverAuth # Predefined value - clientAuth # Predefined value - "1.3.6.1.5.5.7.3.17" # Custom OID (Microsoft Document Signing) - "1.2.3.4.5.6.7" # Organization-specific OIDOID format requirements:
- Dot-separated integers (e.g.,
1.2.3.4.5) - Must have at least 2 components
- Components must be non-negative integers
- Must be quoted in YAML to prevent parsing issues
Common custom OIDs:
| OID | Description |
|---|---|
1.3.6.1.5.5.7.3.17 | Microsoft Document Signing |
1.3.6.1.4.1.311.20.2.2 | Microsoft Smart Card Logon |
1.3.6.1.5.2.3.5 | Kerberos PKINIT Client Authentication |
Basic Constraints
Section titled “Basic Constraints”extensions: basicConstraints: critical: true # RFC 5280: MUST be critical for CA ca: true # true for CA, false for end-entity pathLen: 0 # Optional: max intermediate CAs (0 = no intermediates)Certificate Policies
Section titled “Certificate Policies”extensions: certificatePolicies: critical: false policies: - oid: "2.23.140.1.2.1" # CA/Browser Forum DV cps: "http://example.com/cps" # CPS URL userNotice: "Certificate issued under DV policy" # Optional noticeName Constraints (CA only)
Section titled “Name Constraints (CA only)”Restricts which names a CA can issue certificates for. Only valid for CA certificates.
extensions: nameConstraints: critical: true # RFC 5280: MUST be critical permitted: dns: - ".example.com" # Can issue for *.example.com - "example.com" # Can issue for example.com email: - "@example.com" # Can issue for *@example.com ip: - "10.0.0.0/8" # CIDR notation - "192.168.0.0/16" excluded: dns: - ".forbidden.com" # Cannot issue for *.forbidden.comOCSP No Check
Section titled “OCSP No Check”Indicates that an OCSP responder certificate should not be checked for revocation. Used for OCSP responder certificates to avoid circular dependencies.
extensions: ocspNoCheck: critical: false # RFC 6960 defaultQCStatements (eIDAS Qualified Certificates)
Section titled “QCStatements (eIDAS Qualified Certificates)”The QCStatements extension is used for eIDAS qualified certificates according to ETSI EN 319 412-5. This extension contains statements that qualify the certificate for specific uses under EU regulation 910/2014.
extensions: qcStatements: critical: false # Per ETSI: typically not critical qcCompliance: true # EU qualified certificate (0.4.0.1862.1.1) qcType: esign # Certificate type: esign | eseal | web qcSSCD: true # Key on Qualified Signature Creation Device (0.4.0.1862.1.4) qcRetentionPeriod: 15 # Document retention in years (0.4.0.1862.1.3) qcPDS: # PKI Disclosure Statements (0.4.0.1862.1.5) - url: "https://pki.example.com/pds-en.pdf" language: "en" # ISO 639-1 (2 chars) - url: "https://pki.example.com/pds-fr.pdf" language: "fr"QCStatements Fields
Section titled “QCStatements Fields”| Field | OID | Description |
|---|---|---|
qcCompliance | 0.4.0.1862.1.1 | Certificate is EU qualified (eIDAS) |
qcRetentionPeriod | 0.4.0.1862.1.3 | Document retention period in years |
qcSSCD | 0.4.0.1862.1.4 | Private key on Qualified Signature Creation Device |
qcPDS | 0.4.0.1862.1.5 | PKI Disclosure Statement locations |
qcType | 0.4.0.1862.1.6 | Type of qualified certificate |
QcType Values
Section titled “QcType Values”| Value | OID | Description |
|---|---|---|
esign | 0.4.0.1862.1.6.1 | Electronic signature (natural person) |
eseal | 0.4.0.1862.1.6.2 | Electronic seal (legal person) |
web | 0.4.0.1862.1.6.3 | Website authentication (QWAC) |
QcPDS (PKI Disclosure Statement)
Section titled “QcPDS (PKI Disclosure Statement)”The QcPDS statement references PKI Disclosure Statement documents. Each entry specifies:
url: URL to the PDS document (typically PDF)language: ISO 639-1 language code (2 characters, e.g., “en”, “fr”, “de”)
Multiple PDS locations can be provided for multilingual documents.
Template Variables in QCStatements
Section titled “Template Variables in QCStatements”QcPDS URLs and languages can use template variables:
variables: pds_url: type: uri required: true description: "PKI Disclosure Statement URL" pds_lang: type: string required: true pattern: "^[a-z]{2}$" description: "ISO 639-1 language code"
extensions: qcStatements: qcCompliance: true qcType: esign qcPDS: - url: "{{ pds_url }}" language: "{{ pds_lang }}"eIDAS Certificate Types
Section titled “eIDAS Certificate Types”| Type | Profile | Subject | Use Case |
|---|---|---|---|
| QES | eidas/qc-esign | Natural person (CN, serialNumber) | Qualified electronic signature |
| QESeal | eidas/qc-eseal | Legal person (O, organizationIdentifier) | Qualified electronic seal |
| QWAC | eidas/qc-web | Legal person + domain | Qualified website authentication |
| QTSA | eidas/qc-tsa | TSA service | Qualified timestamping |
Custom Extensions
Section titled “Custom Extensions”For advanced use cases, you can add arbitrary X.509 extensions with custom OIDs and DER-encoded values:
extensions: custom: - oid: "1.2.3.4.5.6.7" critical: false value_hex: "0403010203" # DER value as hexadecimal
- oid: "1.2.3.4.5.6.8" critical: true value_base64: "BAMBAgM=" # DER value as base64Configuration Options
Section titled “Configuration Options”| Field | Required | Description |
|---|---|---|
oid | Yes | Object Identifier in dot notation (e.g., 1.2.3.4.5) |
critical | No | Whether the extension is critical (default: false) |
value_hex | No* | DER-encoded value as hexadecimal string |
value_base64 | No* | DER-encoded value as base64 string |
* At least one of value_hex or value_base64 must be provided, but not both.
Important Notes
Section titled “Important Notes”- The value must be valid DER-encoded ASN.1 data
- You are responsible for correct DER encoding - QPKI passes the value as-is
- Custom extensions are added to
ExtraExtensionsin the certificate template - Use
critical: trueonly if clients MUST understand the extension to process the certificate
Example: Adobe PDF Signing Extension
Section titled “Example: Adobe PDF Signing Extension”extensions: custom: - oid: "1.2.840.113583.1.1.9.1" # Adobe PDF archive timestamp critical: false value_hex: "0500" # ASN.1 NULL (0x05 0x00)Example: Private Enterprise Extension
Section titled “Example: Private Enterprise Extension”extensions: custom: - oid: "1.3.6.1.4.1.99999.1.1" # Your private enterprise OID critical: false value_base64: "MBExDzANBgNVBAMMBnZhbHVlMQ==" # DER-encoded valueDER Encoding Tips
Section titled “DER Encoding Tips”To encode custom values in DER format:
# Create a simple UTF8Stringecho -n "value" | openssl asn1parse -genstr "UTF8:value" -out - | xxd -p
echo "0403010203" | xxd -r -p | openssl asn1parse -inform DER
echo "0403010203" | xxd -r -p | base648. Signature Algorithm Defaults
Section titled “8. Signature Algorithm Defaults”When the signature: field is not specified in a profile, the signature algorithm is automatically inferred from the key algorithm. The following table shows the defaults:
| Key Algorithm | Default Scheme | Default Hash | X.509 Signature Algorithm |
|---|---|---|---|
ecdsa-p256 | ecdsa | sha256 | ECDSAWithSHA256 |
ecdsa-p384 | ecdsa | sha384 | ECDSAWithSHA384 |
ecdsa-p521 | ecdsa | sha512 | ECDSAWithSHA512 |
rsa-2048 | rsassa-pss | sha256 | SHA256WithRSAPSS |
rsa-4096 | rsassa-pss | sha256 | SHA256WithRSAPSS |
ed25519 | ed25519 | (none) | PureEd25519 |
ml-dsa-* | (intrinsic) | (intrinsic) | ML-DSA |
slh-dsa-* | (intrinsic) | (intrinsic) | SLH-DSA |
Override Examples
Section titled “Override Examples”# Use legacy PKCS#1 v1.5 instead of RSA-PSS (for compatibility)algorithm: rsa-4096signature: scheme: pkcs1v15 hash: sha256
algorithm: rsa-4096signature: hash: sha384
algorithm: ecdsa-p384signature: hash: sha512Note: Post-quantum algorithms (ML-DSA, SLH-DSA) have intrinsic signature schemes and do not use the
signature:override.
9. Supported Algorithms
Section titled “9. Supported Algorithms”Signature Algorithms
Section titled “Signature Algorithms”| ID | Algorithm | Type | Security Level |
|---|---|---|---|
ecdsa-p256 | ECDSA with P-256 | Classical | ~128-bit |
ecdsa-p384 | ECDSA with P-384 | Classical | ~192-bit |
ecdsa-p521 | ECDSA with P-521 | Classical | ~256-bit |
ed25519 | Ed25519 | Classical | ~128-bit |
rsa-2048 | RSA 2048-bit | Classical | ~112-bit |
rsa-4096 | RSA 4096-bit | Classical | ~140-bit |
ml-dsa-44 | ML-DSA-44 | PQC | NIST Level 1 |
ml-dsa-65 | ML-DSA-65 | PQC | NIST Level 3 |
ml-dsa-87 | ML-DSA-87 | PQC | NIST Level 5 |
slh-dsa-128f | SLH-DSA-128f | PQC | NIST Level 1 |
slh-dsa-192f | SLH-DSA-192f | PQC | NIST Level 3 |
slh-dsa-256f | SLH-DSA-256f | PQC | NIST Level 5 |
slh-dsa-256s | SLH-DSA-256s | PQC | NIST Level 5 |
KEM Algorithms (Encryption)
Section titled “KEM Algorithms (Encryption)”| ID | Algorithm | Type | Security Level |
|---|---|---|---|
ml-kem-512 | ML-KEM-512 | PQC | NIST Level 1 |
ml-kem-768 | ML-KEM-768 | PQC | NIST Level 3 |
ml-kem-1024 | ML-KEM-1024 | PQC | NIST Level 5 |
10. Usage Examples
Section titled “10. Usage Examples”Direct Issuance with Credential Enroll
Section titled “Direct Issuance with Credential Enroll”# Issue using an ECDSA profileqpki credential enroll --profile ec/tls-server \ --var cn=server.example.com --var dns_names=server.example.com \ --ca-dir ./ca --cred-dir ./credentials
qpki credential enroll --profile hybrid/catalyst/tls-server \ --var cn=server.example.com --var dns_names=server.example.com \ --ca-dir ./ca --cred-dir ./credentials
qpki credential enroll --profile ml/tls-server-sign \ --var cn=server.example.com --var dns_names=server.example.com \ --ca-dir ./ca --cred-dir ./credentialsCSR-Based Issuance
Section titled “CSR-Based Issuance”# Generate CSR firstqpki csr gen --algorithm ecdsa-p256 --keyout server.key \ --cn server.example.com --dns server.example.com --out server.csr
qpki cert issue --profile ec/tls-server --csr server.csr --out server.crt --ca-dir ./caRecommended Profiles by Use Case
Section titled “Recommended Profiles by Use Case”| Use Case | Recommended Profile | Rationale |
|---|---|---|
| Maximum compatibility | ec/tls-server | Works with all modern systems |
| Legacy compatibility | rsa/tls-server | Works with older systems |
| Quantum transition | hybrid/catalyst/tls-server | Classical + PQC in one cert |
| Full post-quantum | ml/tls-server-sign | Pure PQC signature |
| Long-term archive | slh/timestamping | Conservative hash-based |
11. Performance: CompiledProfile
Section titled “11. Performance: CompiledProfile”For high-throughput scenarios (web services, APIs), profiles can be pre-compiled at startup to avoid per-certificate parsing overhead.
Benefits
Section titled “Benefits”| Metric | Standard | Compiled | Improvement |
|---|---|---|---|
| Profile lookup | ~50ns | ~26ns | 2x faster |
| Extensions parsing | Per-cert | Once at load | Eliminated |
| Regex compilation | Per-cert | Once at load | Eliminated |
| CIDR parsing | Per-cert | Once at load | Eliminated |
Usage (Go API)
Section titled “Usage (Go API)”// At startup: compile all profiles oncestore := profile.NewCompiledProfileStore("./profiles")if err := store.Load(); err != nil { log.Fatal(err)}
// Per-request: use pre-compiled profile (26ns lookup, 0 allocs)cp, ok := store.Get("ec/tls-server")if !ok { return errors.New("profile not found")}
// Issue certificate with pre-compiled profileresult, err := ca.EnrollWithCompiledProfile(req, cp)What Gets Pre-Compiled
Section titled “What Gets Pre-Compiled”- KeyUsage: String values →
x509.KeyUsagebits - ExtKeyUsage: String values →
x509.ExtKeyUsageconstants - BasicConstraints: Parsed once
- NameConstraints: CIDR strings →
net.IPNetstructures - Variable patterns: Regex strings →
*regexp.Regexp - CIDR ranges: IP ranges parsed once
See Also
Section titled “See Also”- CA - CA initialization and certificate issuance
- Credentials - Credential enrollment with profiles
- Keys - Key generation and CSR operations
- Post-Quantum - Catalyst and PQC concepts
- CLI Reference - Complete command reference