# Certificate management

> Create, configure, validate, and rotate the SEB configuration-encryption identity — the certificate that decides which devices can open a configuration.

[Canonical HTML page](https://safeonlineexam.com/docs/deploy/certificates)

By default, deployments encrypt generated `.seb` files to a configured public
X.509 certificate. The service holds **only the public certificate**; the matching
private identity is installed only on approved exam devices. This is one of the
[two independent protections](/docs/how-it-works#two-independent-protections) —
encryption stops an unapproved device from opening a configuration at all, while
Config Key proof stops an access code from being released against stale settings.

<Callout type="warn" title="Compatibility mode is a deliberate trade-off">
  An instance may set `SEB_CONFIG_ENCRYPTION_ENABLED=false` when it cannot distribute
  a private identity. Config Key proof remains required, but the downloaded file is
  not device-specific and is plaintext unless an instructor sets a start password.
  Approve and document that lower-assurance posture; never introduce it as a
  shortcut during a certificate incident.
</Callout>

## Trust model [#trust-model]

| Component                        | Holds                                                       | Must **not** hold                                                      |
| -------------------------------- | ----------------------------------------------------------- | ---------------------------------------------------------------------- |
| Application runtime              | Public certificate (or permitted local public-key fallback) | Private key, `.p12`, passphrase                                        |
| Secret manager                   | Public certificate PEM                                      | Private key or `.p12`                                                  |
| Device-management system / vault | Private identity + its protection material                  | Broad user or runtime access                                           |
| Approved SEB client              | Non-extractable, SEB-scoped private identity                | An exportable identity available to a student account or unrelated app |

## Generate an identity [#generate-an-identity]

The repository generator writes private artifacts under the git-ignored `.local/`
directory. Put the passphrase in a protected file — never in a command argument or
environment variable.

```bash
umask 077
mkdir -p .local
openssl rand -base64 48 > .local/seb-cert-p12-password

SEB_CERT_NAME=seb-config-encryption \
SEB_CERT_SUBJECT="/CN=Safe Online Exam Configuration Encryption/O=Organization" \
bash scripts/generate-seb-config-cert.sh \
  .local/seb-certs \
  .local/seb-cert-p12-password
```

It produces:

<Files>
  <File name="seb-config-encryption.crt.pem" annotation="Public certificate for the service" />

  <File name="seb-config-encryption.cer" annotation="Public certificate, DER format" />

  <File name="seb-config-encryption.key.pem" annotation="Private key — restricted bootstrap artifact" />

  <File name="seb-config-encryption.p12" annotation="Private client identity — restricted bootstrap artifact" />
</Files>

<Callout type="warn" title="Move the private artifacts immediately">
  Move the private PEM, `.p12`, and passphrase into approved restricted storage, then
  remove workstation copies once the vault upload and client deployment are verified.
  Never commit, log, email, attach, or pass these through command arguments, tickets,
  or chat.
</Callout>

## Configure the service [#configure-the-service]

<Tabs items="[&#x22;Google Cloud&#x22;, &#x22;Docker / VPS&#x22;]">
  <Tab value="Google Cloud">
    Store the public certificate in Secret Manager and inject it as
    `SEB_CONFIG_ENCRYPTION_CERT_PEM`. The Cloud Run release-bundle installer creates
    the instance-prefixed Secret Manager secret and pins the deployed revision to its
    numbered version; follow the [Google Cloud guide](/docs/deploy/google-cloud)
    rather than reusing a shared development/production secret name.
  </Tab>

  <Tab value="Docker / VPS">
    Provide the public certificate as a protected runtime file at the path referenced
    by `SEB_CONFIG_ENCRYPTION_CERT_PATH` — for the checked-in Compose secret mount,
    `secrets/seb-config-encryption.crt.pem`.
  </Tab>
</Tabs>

The service validates the certificate at startup and at every download. When
encryption is enabled in a hardened runtime, a valid public X.509 certificate is
required and a public-key-only fallback is **not** sufficient. In local
development, a public-key fallback remains available for configuration work.

The configured certificate is served for verification at:

```text
${TOOL_URL}/seb/config-encryption-certificate.pem
${TOOL_URL}/seb/config-encryption-certificate.cer
```

These never serve private material. Their `x-seb-public-key-hash` response header
is what you compare against the client identity during rollout checks. In explicit
plaintext compatibility mode, neither endpoint is served.

## Client deployment (summary) [#client-deployment-summary]

Prefer your device-management platform's native certificate/profile mechanism.
The profile should install the PKCS#12 identity into the intended scope, mark the
private key **non-extractable** where supported, restrict it to the approved SEB
application, prevent removal by an ordinary student account, and be scoped to a
test group first.

For a managed macOS fleet whose MDM cannot install the needed identity in the user
keychain, the release bundle includes a controlled staged-file fallback. The MDM
must stage the `.p12` and its passphrase only in root-owned, mode-0600 files, then
run `install-seb-config-identity-user-keychain.sh` as root after the intended user
has logged in. The helper verifies the identity fingerprint and private-key match,
validates the approved SEB application, and imports through that user's GUI
security session without exposing the private identity to the student account or
passing the passphrase to `security import`.

Never put the identity, passphrase, or login-keychain password in an MDM script
parameter. If a user session or keychain is not ready, the helper returns a
retryable status instead of weakening the installation. A signed in-house package
is the preferred Jamf School fallback when its native payload cannot reach the
required user keychain; build and scope it from a secured administrator workflow
only. It is a compatibility path for managed standard-user Macs, not a substitute
for hardware-backed device identity. Full platform controls are in
[Device deployment](/docs/operate/devices).

## Pre-assessment validation when encryption is enabled [#pre-assessment-validation-when-encryption-is-enabled]

Before a rollout window:

<Steps>
  <Step>
    Confirm the active revision has encryption enabled and the expected certificate secret version or mounted file.
  </Step>

  <Step>
    Request the public certificate endpoint and record its 

    `x-seb-public-key-hash`

    .
  </Step>

  <Step>
    Confirm the client profile reports installed on every intended test device.
  </Step>

  <Step>
    On an approved test device, run the setup check and open the encrypted setup configuration.
  </Step>

  <Step>
    Confirm Config Key proof succeeds after SEB starts.
  </Step>

  <Step>
    Confirm a device 

    **without**

     the identity cannot open the encrypted configuration.
  </Step>

  <Step>
    Confirm an unrelated app and an ordinary student account cannot export or use the private key.
  </Step>

  <Step>
    Confirm the certificate remains valid for the assessment and recovery window.
  </Step>
</Steps>

## Rotation [#rotation]

Perform routine rotation **outside** active assessments:

<Steps>
  <Step>
    Generate and secure a replacement identity.
  </Step>

  <Step>
    Create the matching non-extractable, SEB-restricted client profile.
  </Step>

  <Step>
    Deploy it to a test group and pass the setup check.
  </Step>

  <Step>
    Deploy it to all intended clients, with a planned overlap period if needed.
  </Step>

  <Step>
    Add the replacement public certificate to the service secret and deploy a new revision.
  </Step>

  <Step>
    Verify the active public-key hash and download fresh configurations.
  </Step>

  <Step>
    Remove the old profile and revoke/delete the old private material after the overlap window.
  </Step>
</Steps>

Any relevant SEB settings change invalidates old configurations — tell students to
download a fresh `.seb` file after rotation. If the private identity is suspected
compromised, pause the affected workflow, rotate, deploy the replacement public
certificate, invalidate affected settings through the normal flow, and require
fresh configurations. Do not switch to plaintext compatibility mode reactively
during an incident; treat it as a separately reviewed deployment decision.

## Troubleshooting [#troubleshooting]

| Symptom                                                 | Check                                                                                                                                    |
| ------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| SEB cannot open a downloaded configuration              | Confirm a fresh download, the profile is installed in the correct scope, and the public-key hash matches the active service certificate. |
| A working device fails after rotation                   | Confirm the new profile arrived **before** the service switched certificates, then download a fresh configuration.                       |
| Service fails startup or download creation              | Confirm encryption is enabled, the certificate is a currently valid X.509, and the secret value retained its PEM line breaks.            |
| A private key is exportable or usable by unrelated apps | Stop the rollout and correct the device-management profile. Do not weaken application restrictions.                                      |

More symptoms are in
[Troubleshooting → SEB configuration](/docs/troubleshooting/seb-configuration).