Magistrala
Dev Guide

Certs

Issue, renew and revoke device/gateway certificates for mTLS through Atom's certificate API.

Certificate issuance is provided by Atom, for mutual TLS between devices/gateways and the platform. There is no standalone certs HTTP service or CLI command — everything below is Atom's GraphQL API, same connection details as the rest of this reference: POST http://localhost:8080/graphql, Content-Type: application/json, Authorization: Bearer <user_token>.

Configuration

Certificate issuance is enabled per deployment:

ATOM_CERTS_ENABLED=true
ATOM_CERTS_CA_MODE=file_root_issuer
ATOM_CERTS_ROOT_CA_CERT_PATH=/certs/ca.crt
ATOM_CERTS_ROOT_CA_KEY_PATH=/certs/ca.key

ATOM_CERTS_CA_DIR (defaulting to ./ssl/certs) is mounted into the Atom container as /certs, so the root CA cert/key paths above are read from there. ATOM_CERTS_LEAF_DEFAULT_TTL_SECS and ATOM_CERTS_LEAF_MAX_TTL_SECS bound how long an issued leaf certificate is valid for.

Issue a certificate

Two ways to issue: Atom generates the key pair for you, or you supply your own CSR.

Generated key pair

curl -sSiX POST http://localhost:8080/graphql \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <user_token>" \
-d @- <<EOF
{
  "query": "mutation IssueCert(\$input: IssueGeneratedCertificateV2Input!) { issueGeneratedCertificateV2(input: \$input) { certificate { credentialId serialNumber expiresAt } privateKeyPem chainPem } }",
  "variables": {
    "input": {
      "entityId": "<device_id>",
      "ttlSecs": 31536000
    }
  }
}
EOF

The response's privateKeyPem is only returned once, at issuance — Atom does not store it.

From a CSR

curl -sSiX POST http://localhost:8080/graphql \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <user_token>" \
-d @- <<EOF
{
  "query": "mutation IssueFromCSR(\$input: IssueCertificateFromCsrV2Input!) { issueCertificateFromCsrV2(input: \$input) { certificate { credentialId serialNumber expiresAt } } }",
  "variables": {
    "input": {
      "entityId": "<device_id>",
      "csrPem": "<csr_pem_content>",
      "ttlSecs": 31536000,
      "idempotencyKey": "<client_generated_uuid>"
    }
  }
}
EOF

idempotencyKey is required on the CSR path — a retried request with the same key replays the original result (idempotentReplay: true) instead of issuing a second certificate.

View / list certificates

curl -sSiX POST http://localhost:8080/graphql \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <user_token>" \
-d @- <<EOF
{
  "query": "query Certs(\$entityId: ID, \$status: String, \$limit: Int, \$offset: Int) { certificates(entityId: \$entityId, status: \$status, limit: \$limit, offset: \$offset) { total items { credentialId serialNumber status expiresAt } } }",
  "variables": { "entityId": "<device_id>", "status": "active", "limit": 20, "offset": 0 }
}
EOF

A single certificate can be fetched with certificate(credentialId: ID!).

Renew a certificate

curl -sSiX POST http://localhost:8080/graphql \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <user_token>" \
-d @- <<EOF
{
  "query": "mutation RenewCert(\$input: RenewGeneratedCertificateV2Input!) { renewGeneratedCertificateV2(input: \$input) { certificate { credentialId serialNumber expiresAt } privateKeyPem } }",
  "variables": {
    "input": {
      "credentialId": "<credential_id>",
      "ttlSecs": 31536000,
      "revokeOld": true,
      "idempotencyKey": "<client_generated_uuid>"
    }
  }
}
EOF

renewCertificateFromCsrV2 is the CSR-based equivalent, taking credentialId/csrPem instead of generating a new key pair. revokeOld: true revokes the certificate being renewed once the new one is issued.

Revoke a certificate

curl -sSiX POST http://localhost:8080/graphql \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <user_token>" \
-d @- <<EOF
{
  "query": "mutation RevokeCert(\$input: RevokeCertificateV2Input!) { revokeCertificateV2(input: \$input) { certificate { credentialId status } } }",
  "variables": { "input": { "credentialId": "<credential_id>", "reason": "device decommissioned" } }
}
EOF

RevokeCertificateV2Input also accepts serialNumber or fingerprintSha256 in place of credentialId. To revoke everything an entity holds, use revokeEntityCertificates(entityId: ID!, reason: String): Int! — it returns the number revoked. For a workspace- or issuer-wide sweep, bulkRevokeCertificates pages through matching certificates via afterCredentialId/snapshotAt cursors.

CA, CRL and OCSP

These are plain HTTPS routes served directly by Atom, not GraphQL:

curl https://<atom-host>/certs/trust-bundle.pem
curl https://<atom-host>/certs/issuers/<issuer_id>/crl
curl https://<atom-host>/certs/issuers/<issuer_id>/ocsp

ATOM_PUBLIC_URL (or ATOM_PUBLIC_BASE_URL) must be set for these routes to be reachable at a stable, publicly-resolvable address — devices validating a peer's chain or checking revocation status need a URL they can actually reach, not localhost.

On this page