> For the complete documentation index, see [llms.txt](https://mercure-technologies.gitbook.io/xprem/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://mercure-technologies.gitbook.io/xprem/key-store/keys.md).

# Keys

The server relies on three different keys:

| Key                                                                                                        | Scope       | Needed when                                      |
| ---------------------------------------------------------------------------------------------------------- | ----------- | ------------------------------------------------ |
| **Expo signing key pair** — signs every manifest served to `expo-updates`; clients verify updates with it. | One per app | Always                                           |
| **Database master key** — encrypts the app signing keys stored in PostgreSQL. It is not a signing key.     | Server-wide | Always                                           |
| **CloudFront private key** — signs CDN asset URLs.                                                         | Server-wide | Only with [CloudFront](/xprem/cdn/cloudfront.md) |

### The signing key pair of each app

When you create an app from the dashboard or through the API, you choose where its signing keys live.

| Store                                     | Who generates the pair | How it works                                                                                                                                                                                                                           |
| ----------------------------------------- | ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `database` (preselected in the dashboard) | The server             | It generates an RSA pair when the app is created and seals it in PostgreSQL with the master key. You then download the certificate from the app page in the dashboard (`app-<id>-certificate.txt`) and commit it to your Expo project. |
| `aws-secrets-manager`                     | You                    | You run `npx eoas generate-certs`, store the two PEM files as AWS secrets, and reference their secret IDs when creating the app. The database stores no key material.                                                                  |

You can freely mix stores on the same server — one app on `database`, another on `aws-secrets-manager`. File paths and environment variables are not offered here, because they cannot be provisioned per app for every replica.

{% hint style="info" %}
If any of your apps uses `aws-secrets-manager`, the server needs access to Secrets Manager: `AWS_REGION` plus an IAM role (or `AWS_ACCESS_KEY_ID`/`AWS_SECRET_ACCESS_KEY`) allowed to call `secretsmanager:GetSecretValue` on both secrets.
{% endhint %}

#### Migrating from stateless mode

If you started on a v2 or stateless deployment and later point the server at a database, the migration happens on its own: the server reads your local or environment keys once and seals them into the database with the master key. Your clients keep verifying updates with the same pair, so there is no key rotation to worry about.

### The database master key

The server needs a master key to seal the app signing pairs at rest. Provide it through exactly one of these variables:

| Variable                             | Source                     |
| ------------------------------------ | -------------------------- |
| `DB_KEYS_MASTER_KEY_B64`             | Environment variable       |
| `AWSSM_DB_KEYS_MASTER_KEY_SECRET_ID` | AWS Secrets Manager secret |

You can generate a valid key with:

```bash
openssl rand -base64 32
```

If you set both variables, or neither, the server refuses to start.

{% hint style="danger" %}
The master key is not recoverable, so back it up before your first deployment. If you lose it, every signing key stored in the database becomes unreadable.
{% endhint %}

### The CloudFront private key

A single server-wide key signs the CDN asset URLs, independently of the per-app signing keys.

If `KEYS_STORAGE_TYPE` is set, the server reads the key only from the source that matches it:

| `KEYS_STORAGE_TYPE`   | CloudFront key source                    |
| --------------------- | ---------------------------------------- |
| `aws-secrets-manager` | `AWSSM_CLOUDFRONT_PRIVATE_KEY_SECRET_ID` |
| `environment`         | `PRIVATE_CLOUDFRONT_KEY_B64`             |
| `local`               | `PRIVATE_CLOUDFRONT_KEY_PATH`            |

If another source is configured, it is ignored — and when the selected source turns out to be empty, the server logs a warning at startup so the misconfiguration doesn't go unnoticed.

If `KEYS_STORAGE_TYPE` is not set, the server simply tries the three sources in order: Secrets Manager, then the base64 variable, then the file path.

See [CloudFront](/xprem/cdn/cloudfront.md) to generate and configure the key pair itself.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://mercure-technologies.gitbook.io/xprem/key-store/keys.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
