Platform
ScaiWave ScaiGrid ScaiCore ScaiBot ScaiDrive ScaiKey Models Tools & Services
Solutions
Organisations Developers Internet Service Providers Managed Service Providers AI-in-a-Box
Resources
Support Documentation Blog Downloads
Company
About Research Careers Investment Opportunities Contact
Log in

PKI

ScaiVault ships a full X.509 PKI: you can run an internal Certificate Authority, integrate ACME providers (Let's Encrypt and friends) for public certificates, sign external CSRs, and manage a trust store — all through the same API and audit trail as regular secrets.

What's in it#

  • Internal CAs. Root and intermediate CAs generated and signed by ScaiVault.
  • Issued certificates. Server certs, client certs, mTLS pairs — issued from an internal CA against a role.
  • CSR workflow. Submit a CSR, get it reviewed/approved, sign it. Useful when the requester can't generate keys inside ScaiVault.
  • ACME. Let's Encrypt, ZeroSSL, BuyPass, Google, or any RFC 8555 endpoint. ScaiVault acts as the ACME client.
  • Trust anchors. External CA certificates you want your stack to trust.
  • CRLs. Automatic certificate revocation list generation.

All of this is under /v1/pki/, scoped to the tenant.

The pieces in a hierarchy#

graph TB Root[Acme Root CA<br/>10-year validity<br/>kept offline] Int1[Intermediate: mTLS<br/>5-year validity<br/>online issuer] Int2[Intermediate: External<br/>5-year validity] Role1[Role: svc-mtls<br/>*.svc.cluster.local<br/>max_ttl 720h] ACME[Account: letsencrypt-prod<br/>via ACME] Leaf1[billing.svc.cluster.local<br/>7-day cert] Leaf2[reporting.svc.cluster.local<br/>7-day cert] Leaf3[api.acme.example<br/>90-day ACME cert] Root --> Int1 Root --> Int2 Int1 --> Role1 Role1 --> Leaf1 Role1 --> Leaf2 ACME --> Leaf3

When to use which#

You need Use
mTLS between internal services Internal CA, issue against a role
TLS for a public hostname ACME
Sign a cert request from a vendor or external party CSR workflow
Trust a third-party CA in your clients Trust anchor
Revoke a cert and publish the CRL Revoke + generate CRL

Mixing is fine. Most deployments end up with both an internal CA (for service-to-service mTLS) and ACME (for public ingress).

Internal CA#

Create a root CA:

bash
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
curl -X POST https://scaivault.scailabs.ai/v1/pki/ca \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "acme-root",
    "common_name": "Acme Root CA",
    "ca_type": "root",
    "key_type": "ec",
    "key_size": 256,
    "validity_days": 3650
  }'

Then an intermediate signed by the root:

bash
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
curl -X POST https://scaivault.scailabs.ai/v1/pki/ca \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "acme-intermediate-mtls",
    "common_name": "Acme mTLS Intermediate",
    "ca_type": "intermediate",
    "parent_ca_id": "ca_root_abc",
    "validity_days": 1825
  }'

Roots should stay offline in practice — generate one, export the certificate, put the CA backup in cold storage, and issue from an intermediate. ScaiVault supports root-CA export via GET /v1/pki/ca/{id}/certificate.

PKI roles#

A role constrains what kind of certificates an issuer can mint: which domain names, which key types, what maximum TTL. Roles exist so you can grant "issue mTLS certs for services under *.svc.cluster.local" without granting "issue any cert for any name".

bash
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
curl -X POST https://scaivault.scailabs.ai/v1/pki/roles \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "svc-mtls",
    "ca_id": "ca_intermediate_mtls",
    "allowed_domains": ["*.svc.cluster.local", "*.internal"],
    "allow_subdomains": true,
    "allow_ip_sans": true,
    "max_ttl": "720h",
    "key_type": "ec",
    "key_bits": 256,
    "require_cn": true
  }'

Issue a cert against the role:

bash
1
2
3
4
5
6
7
8
9
curl -X POST https://scaivault.scailabs.ai/v1/pki/issue/svc-mtls \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "common_name": "billing.svc.cluster.local",
    "alt_names": ["billing-api.svc.cluster.local"],
    "ip_sans": ["10.0.5.100"],
    "ttl": "168h"
  }'

Response:

json
1
2
3
4
5
6
7
{
  "certificate": "-----BEGIN CERTIFICATE-----\n...",
  "private_key": "-----BEGIN EC PRIVATE KEY-----\n...",
  "ca_chain": ["-----BEGIN CERTIFICATE-----\n..."],
  "serial_number": "1A:2B:3C:...",
  "not_after": "2026-04-30T20:00:00Z"
}

The private key is returned once. Store it yourself; ScaiVault does not keep plaintext private keys for issued certs by default (you can opt in via role config).

CSR workflow#

When the requester controls key generation — typical for IoT devices, air-gapped systems, vendors — they submit a CSR and you sign it.

bash
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# 1. Requester generates key and CSR locally, sends CSR to you.

# 2. You import the CSR
curl -X POST https://scaivault.scailabs.ai/v1/pki/csr/import \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"csr_pem": "-----BEGIN CERTIFICATE REQUEST-----\n..."}'
# → {"id": "csr_abc", "status": "pending", ...}

# 3. Review, then approve
curl -X POST https://scaivault.scailabs.ai/v1/pki/csr/csr_abc/approve \
  -H "Authorization: Bearer $TOKEN"

# 4. Sign it against a CA
curl -X POST https://scaivault.scailabs.ai/v1/pki/csr/csr_abc/sign \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"ca_id": "ca_intermediate_mtls", "validity_days": 90}'

Rejected CSRs (POST /csr/{id}/reject) are recorded with a reason for audit.

ACME#

Let's Encrypt, ZeroSSL, BuyPass, Google — anything speaking RFC 8555. ScaiVault is the ACME client; you operate the CA's ACME endpoint indirectly by asking ScaiVault to handle an order.

bash
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
# 1. Register an ACME account
curl -X POST https://scaivault.scailabs.ai/v1/pki/acme/accounts \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "letsencrypt-production",
    "provider": "letsencrypt",
    "environment": "production",
    "email": "certs@acme.example"
  }'

# 2. Request a certificate
curl -X POST https://scaivault.scailabs.ai/v1/pki/acme/issue \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "account_id": "acme_abc",
    "domains": ["api.acme.example", "www.acme.example"],
    "challenge_type": "dns-01",
    "auto_renew": true
  }'

Challenge types:

  • http-01 — ScaiVault serves the challenge token from .well-known/acme-challenge/... on port 80. You point your ACME-target traffic through ScaiVault.
  • dns-01 — ScaiVault writes a TXT record via a configured DNS provider (Route 53, Cloudflare, Google, and others — see DNS providers list). Works for wildcards and for hosts not reachable on port 80.
  • tls-alpn-01 — ScaiVault responds on port 443 with a special ALPN. Useful when port 80 is closed.

Auto-renew defaults to on. ScaiVault re-runs the ACME flow ~30 days before expiration, writes the new certificate as a new version of the same managed object, and fires certificate.renewed.

Revocation#

bash
1
2
3
4
5
6
7
curl -X POST https://scaivault.scailabs.ai/v1/pki/revoke \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "serial_number": "1A:2B:3C:...",
    "reason": "key_compromise"
  }'

Reasons follow RFC 5280: unspecified, key_compromise, ca_compromise, affiliation_changed, superseded, cessation_of_operation, certificate_hold, privilege_withdrawn, aa_compromise.

CRLs and OCSP#

Every internal CA has a CRL that ScaiVault regenerates automatically (default: every hour). Fetch it:

bash
1
curl https://scaivault.scailabs.ai/v1/pki/ca/{ca_id}/crl

Force an immediate regeneration: POST /v1/pki/ca/{ca_id}/crl.

OCSP responders for each CA are available at /v1/pki/ocsp/{ca_id}.

Trust anchors#

External CA certs you want to trust — e.g., a vendor's CA for validating their mTLS connections.

bash
1
2
3
4
5
6
7
curl -X POST https://scaivault.scailabs.ai/v1/pki/trust-anchors \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "vendor-xyz-root",
    "certificate_pem": "-----BEGIN CERTIFICATE-----\n..."
  }'

Trust anchors are used by POST /v1/pki/certificates/validate to verify chains against your trust store.

What's next#

Updated 2026-05-17 14:30:19 View source (.md) rev 5