Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions docs/reference/cryptosctl.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,14 @@ Notes:
- `ca import-key` asks for the backup's passphrase, and refuses a node that already has an identity.
- Step-by-step guides in the `cryptos-node` repository: [subordinating vCenter's VMCA](https://github.com/CryptOS-PKI/cryptos-node/blob/main/docs/vmca-subordination.md) and [re-certifying a subordinate](https://github.com/CryptOS-PKI/cryptos-node/blob/main/docs/subordinate-recertify.md).

## Time-stamp authority

| Command | Flags | What it does |
|---|---|---|
| `tsa certificates` | `--pem` | list every TSA certificate the node has signed timestamps with, newest first, marking the current one; `--pem` prints the certificates instead |

The list includes past certificates, and answers whether or not the TSA runs this boot, so tokens signed before a rotation can still be verified. See [Serve RFC 3161 timestamps](../using/serve-timestamps-tsa.md).

## Audit log

Both commands read the node's hash-chained audit log and change nothing. Over mutual TLS they need the bootstrap admin certificate, like `ca list-issued`. A node in maintenance mode answers `FailedPrecondition`. The walk-through is [Check the audit log](../using/audit-log.md).
Expand Down
58 changes: 58 additions & 0 deletions docs/reference/machine-config-tsa.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
---
title: "📄 Machine config: TSA"
---

# 📄 Machine config: TSA

:::info[Arrives with cryptos-node#354]
This page describes the `pki.tsa` block as [CryptOS-PKI/cryptos-node#354](https://github.com/CryptOS-PKI/cryptos-node/pull/354) adds it.
:::

The `pki.tsa` block of the [machine config](./machine-config.md): the node's RFC 3161 time-stamp authority. The how-to is [Serve RFC 3161 timestamps](../using/serve-timestamps-tsa.md).

The TSA is off unless the block is present, and it follows the same switching rules as ACME and EST: [Switching a protocol on or off](./machine-config-enrollment.md#-switching-a-protocol-on-or-off). `enabled: false` switches it off and keeps the settings, an `ApplyConfig` that leaves the block out keeps what the node has, and every change takes effect at the next reboot.

:::caution[A Root never serves a TSA]
A config that switches `pki.tsa` on at a Root is refused, and nothing is stored. A block with `enabled: false` is accepted and never served.
:::

```yaml
pki:
tsa:
http_port: 318
policy_oid: "1.3.6.1.4.1.32473.1.1"
accuracy_ms: 1000
rate_limit:
requests_per_minute: 60
burst: 60
allowed_networks: [10.20.0.0/16, "2001:db8:20::/48", 192.0.2.44]
certificate:
validity_days: 365
rotation_overlap_days: 30
```

| Field | Type | Default | Meaning |
|---|---|---|---|
| `enabled` | bool | `true` when the block is present | `false` switches the TSA off and keeps the other settings. |
| `http_port` | int | `318` (`0` means the default) | TCP port of the plain-HTTP listener. Requests are POSTed to its root path. |
| `policy_oid` | string | none, required | The TSA policy every token names, in dotted form, under your own enterprise number: [Get a TSA policy OID](../using/tsa-policy-oid.md). At least two decimal arcs without leading zeros, a first arc of 0, 1 or 2, and a second arc below 40 when the first is 0 or 1. A request for another policy is refused with `unacceptedPolicy`. |
| `accuracy_ms` | int | `1000` (`0` means the default) | The accuracy every token claims, in milliseconds either side of its time. At most `60000`. While the latest time sync measured a larger offset, every request is refused with `timeNotAvailable`. |
| `rate_limit.requests_per_minute` | int | `60` (`0` means the default) | The steady rate one client may sustain. A client is its IPv4 address, or its IPv6 /64. The limit cannot be switched off. |
| `rate_limit.burst` | int | `requests_per_minute` | How many requests a client may make at once before the rate applies. Over the limit a client gets HTTP 429 with `Retry-After`. |
| `allowed_networks` | list | empty: anyone | IPv4 or IPv6 CIDR prefixes, or bare addresses for one host. Anyone else gets HTTP 403 before the request is read. |
| `certificate.validity_days` | int | `365` (`0` means the default) | Lifetime of the TSA certificate, at most `365`, and never past the CA certificate's own expiry. |
| `certificate.rotation_overlap_days` | int | `30` (`0` means the default) | How long before the TSA certificate expires the node issues its successor and signs with it. Must be shorter than `validity_days`. |

Errors you may see from `cryptosctl config apply`:

| Message starts with | Cause |
|---|---|
| `config: pki.tsa.policy_oid: required` | The block is enabled without a policy OID. |
| `config: pki.tsa.policy_oid: "..."` | The OID breaks one of the rules above. |
| `config: pki.tsa: must not be enabled on a root node` | The node is a Root. |
| `config: pki.tsa.accuracy_ms` | More than `60000`. |
| `config: pki.tsa.allowed_networks[N]` | Entry `N` is not an address or a CIDR prefix. |
| `config: pki.tsa.certificate.validity_days` | More than `365`. |
| `config: pki.tsa.certificate.rotation_overlap_days` | The overlap is not shorter than the validity. With the default overlap of 30 days, that includes any `validity_days` of 30 or less. |

In the API's `MachineConfig` the block is `Pki.tsa` (`Tsa`, `TsaRateLimit`, `TsaCertificateSettings`). `NodeService.ListTsaCertificates` returns every TSA certificate the node has signed with, and `NodeStatus.protocols` reports `SERVICE_PROTOCOL_TSA`.
2 changes: 1 addition & 1 deletion docs/use-cases/code-signing.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ The node refuses a CSR for any other key, with `subject RSA key must be at least

A signature is only as good as the certificate behind it, and the certificate expires. A signing tool that adds an RFC 3161 **timestamp** records that the signature was made while the certificate was still valid, so the signature keeps validating afterwards.

CryptOS does not serve timestamps. Either point your signing tool at another RFC 3161 timestamp service, or plan for signatures to stop validating on platforms that check expiry once the certificate runs out.
An intermediate or issuing node can serve those timestamps itself, as an RFC 3161 time-stamp authority, once [CryptOS-PKI/cryptos-node#354](https://github.com/CryptOS-PKI/cryptos-node/pull/354) is in the image you run: [Serve RFC 3161 timestamps](../using/serve-timestamps-tsa.md). Point the signing tool at it, for example `signtool sign /tr http://pki-issuing.example.org:318/ /td sha256`. On an image without it, use another RFC 3161 timestamp service, or plan for signatures to stop validating on platforms that check expiry once the certificate runs out.

## If a signing key leaks

Expand Down
2 changes: 1 addition & 1 deletion docs/use-cases/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ A config that sets `pki.acme` or `pki.est` on a Root is refused, and nothing is

The Fleet Manager lists each enrolment protocol adapter with an **Enabled** switch. As the page itself says, enabling records intent: it does not start the protocol on a node.

The node does not serve SCEP, ACME `dns-01` or RFC 3161 timestamps.
The node does not serve ACME `dns-01`. SCEP and RFC 3161 timestamps arrive with their own pages: [Enrol devices with SCEP](../using/enrol-devices-scep.md) and [Serve RFC 3161 timestamps](../using/serve-timestamps-tsa.md).

## One rule for every key

Expand Down
160 changes: 160 additions & 0 deletions docs/using/serve-timestamps-tsa.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
---
title: "⏱️ Serve RFC 3161 timestamps"
---

import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

# ⏱️ Serve RFC 3161 timestamps

:::info[Arrives with cryptos-node#354]
This page describes the time-stamp authority as [CryptOS-PKI/cryptos-node#354](https://github.com/CryptOS-PKI/cryptos-node/pull/354) adds it. It is true once that change is in the image you run.
:::

Switch on the RFC 3161 time-stamp authority (TSA) on an intermediate or issuing node, so code signed with a CryptOS certificate keeps verifying after the certificate expires.

A signing tool sends the TSA a hash of the signature, and gets back a signed token saying when it saw it. The token is stored in the signature. Later, a verifier checks the token instead of today's date against the signing certificate.

The CA key never signs a token. The node issues a separate **TSA certificate** from its CA, with the extended key usage `id-kp-timeStamping` marked critical, and signs tokens with that. Its key lives where the CA key does: in the TPM on a TPM node.

:::info[Before you start]
- An intermediate or issuing node, installed and running. A Root never serves a TSA.
- `cryptosctl` set up to reach the node: [Setup](./setup.md).
- The node's time in sync, with `network.ntp_servers` or a DHCP lease that carries NTP servers: [Keep the clock in sync](./time-sync.md).
- A policy OID of your own: [Get a TSA policy OID](./tsa-policy-oid.md).
- The node's CA common name, for the reboot.
:::

## 1. Add the pki.tsa block

Fetch the config as in [Apply config](./config-apply.md), then add a `pki.tsa` block with your policy OID:

```yaml
pki:
tsa:
policy_oid: "1.3.6.1.4.1.32473.1.1"
allowed_networks: [10.20.0.0/16]
```

- `policy_oid` is required. Every token names it. Replace the example number `32473` with your own PEN.
- `allowed_networks` limits which clients may ask. Leave it out to answer anyone. A bare address means one host.
- The listener uses port 318 unless `http_port` sets another.
- Every field, with its default and limits, is in [Machine config: TSA](../reference/machine-config-tsa.md).

:::caution[No policy, no TSA]
A `pki.tsa` block without `policy_oid` is refused with `config: pki.tsa.policy_oid: required`, and nothing is saved. There is no default policy.
:::

:::caution[A Root refuses pki.tsa]
A Root's config with an enabled `pki.tsa` is refused with `config: pki.tsa: must not be enabled on a root node`, and nothing is saved. Serve timestamps from an intermediate or issuing node.
:::

## 2. Apply it and reboot

:::warning[Switching the TSA on needs a reboot in a maintenance window]
Every change to `pki.tsa`, switching it on or off included, reports `requires_reboot=true`. The TSA starts only at boot, and every listener on the node, including issuance, is down while it restarts. Plan the reboot for a maintenance window.
:::

```bash
cryptosctl --endpoint 192.0.2.10:443 config apply -f node.yaml
cryptosctl --endpoint 192.0.2.10:443 reboot --confirm "Example Issuing CA G1"
```

:::tip[Expected output]
After the reboot, `cryptosctl status -o json` lists the TSA under `protocols`, switched on and running:

```json
{ "protocol": "SERVICE_PROTOCOL_TSA", "configured": true, "running": true }
```

`cryptosctl tsa certificates` shows the TSA certificate the node issued, marked current:

```text
SERIAL CURRENT NOT_BEFORE NOT_AFTER SHA256
5cabd044dfe7244b38791e6274c3b0eb5c712079 yes 2026-10-06T19:59:21Z 2027-10-06T20:04:21Z 9f2c...
```
:::

If the TSA is `configured` but not `running` after the reboot, the node could not set it up. The node log says why.

## 3. Ask for a timestamp

On any machine with OpenSSL 3, build a request for a file. `-cert` asks for the TSA certificate in the token, which makes it verifiable on its own:

```bash
openssl ts -query -data artifact.bin -sha256 -cert -out req.tsq
```

Send it to the node:

<Tabs groupId="os" queryString>
<TabItem value="unix" label="Linux / macOS" default>

```bash
curl -sS -H 'Content-Type: application/timestamp-query' --data-binary @req.tsq -o resp.tsr http://192.0.2.10:318/
```

</TabItem>
<TabItem value="windows" label="Windows (PowerShell)">

```powershell
Invoke-WebRequest -Uri http://192.0.2.10:318/ -Method Post -ContentType 'application/timestamp-query' -InFile req.tsq -OutFile resp.tsr
```

</TabItem>
</Tabs>

## 4. Verify the token

Put your root certificate and the node's CA certificate in `chain.pem` (`cryptosctl identity show -o pem` prints the node's chain), then:

```bash
openssl ts -reply -in resp.tsr -text
openssl ts -verify -in resp.tsr -queryfile req.tsq -CAfile chain.pem
```

:::tip[Expected output]
The first command shows `Status: Granted.`, your policy OID and the accuracy:

```text
Policy OID: 1.3.6.1.4.1.32473.1.1
Accuracy: 0x01 seconds, unspecified millis, unspecified micros
Ordering: no
```

The second prints `Verification: OK`.
:::

Point your signing tool at the same URL, for example `signtool sign /tr http://192.0.2.10:318/ /td sha256 ...`, and add `chain.pem` to the trust of every machine that verifies the signatures.

## What the TSA accepts

- **Hashes.** SHA-256, SHA-384 and SHA-512. A request with SHA-1 or MD5 is refused with `badAlg`.
- **Policy.** A request that asks for another policy is refused with `unacceptedPolicy`.
- **Extensions.** None. A request that carries one is refused with `unacceptedExtension`.

## When it refuses

- **`timeNotAvailable` for every request.** The clock is not trusted: no time sync has succeeded this boot, the latest one failed, or it measured an offset larger than `accuracy_ms`. `cryptosctl status` shows the `Clock:` line, and the node log says which. The TSA has no override for this, unlike certificate signing.
- **HTTP 403.** The client is outside `allowed_networks`.
- **HTTP 429.** The client went over its rate limit (60 a minute by default, per IPv4 address or IPv6 /64). Wait for the `Retry-After` seconds.

:::caution[No time source means no timestamps]
A node with no NTP servers, configured or leased, never syncs, so its TSA refuses every request. Set `network.ntp_servers` before you switch the TSA on.
:::

## Rotation and old tokens

The TSA certificate is valid for a year. Thirty days before it expires, the node issues a successor with a new key and signs new tokens with it. It does the same within the hour if the TSA certificate is revoked or the CA key is rotated. No reboot is needed.

Every TSA certificate the node has signed with stays listed, so tokens signed before a rotation still verify:

```bash
cryptosctl --endpoint 192.0.2.10:443 tsa certificates --pem > tsa-certificates.pem
```

The TSA certificate also appears in `cryptosctl ca list-issued` under the profile `tsa`, and with `pki.revocation_base_url` set it carries the node's CRL and OCSP pointers. If its key may have leaked, revoke it with `cryptosctl ca revoke`: the node moves to a new TSA certificate within the hour.

## Switching it off

Set `enabled: false` in the block, apply and reboot. The settings are kept for later, and `cryptosctl tsa certificates` still lists the old certificates.
66 changes: 66 additions & 0 deletions docs/using/tsa-policy-oid.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
---
title: "🪪 Get a TSA policy OID"
---

# 🪪 Get a TSA policy OID

:::info[Arrives with cryptos-node#354]
This page prepares for the time-stamp authority as [CryptOS-PKI/cryptos-node#354](https://github.com/CryptOS-PKI/cryptos-node/pull/354) adds it.
:::

Pick the policy object identifier (OID) your time-stamp authority names in every token, before you [serve timestamps](./serve-timestamps-tsa.md).

Every RFC 3161 token carries a **TSA policy**: an OID that says under which rules the timestamp was issued. CryptOS has no default policy. Each deployment sets its own, so no two deployments share one by accident, and the TSA stays off until `pki.tsa.policy_oid` is set.

The usual way to get an OID of your own is an IANA **Private Enterprise Number** (PEN). It gives your organisation the arc `1.3.6.1.4.1.<PEN>`, and you assign everything under it.

:::info[Before you start]
- The name of your organisation, and a contact address that will stay valid for years: IANA publishes both with the number.
- A place to record what each OID you assign means, such as your PKI policy documents.
:::

## 1. Check whether you already have a PEN

Many organisations already hold one, for SNMP or a previous PKI. Search the published registry for your organisation's name at [the IANA enterprise numbers registry](https://www.iana.org/assignments/enterprise-numbers/).

If you find your organisation, ask whoever owns the number which arc you may use and go to step 3.

## 2. Request a PEN

Fill in the request form at [pen.iana.org](https://pen.iana.org). A PEN is free. IANA reviews the request and emails the number to the contact address; it then appears in the public registry with your organisation's name.

:::caution[The registration is public and long-lived]
The organisation name and contact you enter are published. Use a role address (for example a PKI team mailbox), not a person's own address, so the number does not become unreachable when someone leaves.
:::

## 3. Assign an arc for the TSA policy

Under your PEN, pick a branch for PKI and a number for the TSA policy. Write it down before you use it, so the same OID never means two things. For example, with the PEN `32473`:

| OID | Meaning |
|---|---|
| `1.3.6.1.4.1.32473` | Your organisation (the PEN) |
| `1.3.6.1.4.1.32473.1` | PKI policies |
| `1.3.6.1.4.1.32473.1.1` | TSA policy, version 1 |

:::warning[Do not copy the example number]
`32473` is the example enterprise number reserved for documentation (RFC 5612). A TSA with it in its policy works, but its tokens claim a policy that is not yours. Use your own PEN.
:::

If your TSA's rules change in a way relying parties should know about (a different accuracy, another clock source), give the new rules a new OID, such as `...1.2`, rather than changing what `...1.1` means. Tokens already issued keep naming the old one.

## 4. Check the OID

The node accepts a dotted OID with at least two arcs, each a decimal number without leading zeros, a first arc of 0, 1 or 2, and a second arc below 40 when the first is 0 or 1. Anything under `1.3.6.1.4.1.<PEN>` passes. You can check one with OpenSSL before you apply it:

```bash
openssl asn1parse -genstr OID:1.3.6.1.4.1.32473.1.1
```

:::tip[Expected output]
```text
0:d=0 hl=2 l= 10 prim: OBJECT :1.3.6.1.4.1.32473.1.1
```
:::

Next: [Serve RFC 3161 timestamps](./serve-timestamps-tsa.md).
3 changes: 3 additions & 0 deletions sidebars.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ const sidebars: SidebarsConfig = {
'using/time-sync',
'using/audit-log',
'using/enrol-devices-scep',
'using/tsa-policy-oid',
'using/serve-timestamps-tsa',
],
},
{
Expand Down Expand Up @@ -157,6 +159,7 @@ const sidebars: SidebarsConfig = {
'reference/machine-config',
'reference/machine-config-pki',
'reference/machine-config-enrollment',
'reference/machine-config-tsa',
'reference/cryptosctl',
'reference/grpc-api',
'reference/root-cert-profile',
Expand Down
Loading