@andrewheberle/serverless-ssh-ca 0.22.0 → 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +174 -1
- package/dist/api/operations.d.ts +50 -0
- package/dist/api/operations.d.ts.map +1 -0
- package/dist/api/operations.js +264 -0
- package/dist/api/operations.js.map +1 -0
- package/dist/api/schema.d.ts +72 -0
- package/dist/api/schema.d.ts.map +1 -0
- package/dist/api/schema.js +114 -0
- package/dist/api/schema.js.map +1 -0
- package/dist/api/v3/index.d.ts.map +1 -1
- package/dist/api/v3/index.js +13 -202
- package/dist/api/v3/index.js.map +1 -1
- package/dist/api/v3/schema.d.ts +23 -68
- package/dist/api/v3/schema.d.ts.map +1 -1
- package/dist/api/v3/schema.js +18 -104
- package/dist/api/v3/schema.js.map +1 -1
- package/dist/api/v4/index.d.ts +8 -0
- package/dist/api/v4/index.d.ts.map +1 -0
- package/dist/api/v4/index.js +155 -0
- package/dist/api/v4/index.js.map +1 -0
- package/dist/api/v4/schema.d.ts +268 -0
- package/dist/api/v4/schema.d.ts.map +1 -0
- package/dist/api/v4/schema.js +186 -0
- package/dist/api/v4/schema.js.map +1 -0
- package/dist/certificate.d.ts +9 -6
- package/dist/certificate.d.ts.map +1 -1
- package/dist/certificate.js +55 -99
- package/dist/certificate.js.map +1 -1
- package/dist/db/index.d.ts +68 -9
- package/dist/db/index.d.ts.map +1 -1
- package/dist/db/index.js +165 -49
- package/dist/db/index.js.map +1 -1
- package/dist/db/migrations/0003_krl_revision.d.ts +3 -0
- package/dist/db/migrations/0003_krl_revision.d.ts.map +1 -0
- package/dist/db/migrations/0003_krl_revision.js +14 -0
- package/dist/db/migrations/0003_krl_revision.js.map +1 -0
- package/dist/db/migrations/index.d.ts.map +1 -1
- package/dist/db/migrations/index.js +2 -0
- package/dist/db/migrations/index.js.map +1 -1
- package/dist/db/types.d.ts +2 -0
- package/dist/db/types.d.ts.map +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/krl.d.ts +3 -2
- package/dist/krl.d.ts.map +1 -1
- package/dist/krl.js +12 -165
- package/dist/krl.js.map +1 -1
- package/dist/node/cache.d.ts +21 -0
- package/dist/node/cache.d.ts.map +1 -0
- package/dist/node/cache.js +49 -0
- package/dist/node/cache.js.map +1 -0
- package/dist/node/env.d.ts +6 -3
- package/dist/node/env.d.ts.map +1 -1
- package/dist/node/env.js +4 -3
- package/dist/node/env.js.map +1 -1
- package/dist/node/index.d.ts +1 -0
- package/dist/node/index.d.ts.map +1 -1
- package/dist/node/index.js +1 -0
- package/dist/node/index.js.map +1 -1
- package/dist/proof.d.ts +36 -7
- package/dist/proof.d.ts.map +1 -1
- package/dist/proof.js +53 -21
- package/dist/proof.js.map +1 -1
- package/dist/router.d.ts +25 -1
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +41 -2
- package/dist/router.js.map +1 -1
- package/dist/ssh/certificate.d.ts +93 -0
- package/dist/ssh/certificate.d.ts.map +1 -0
- package/dist/ssh/certificate.js +178 -0
- package/dist/ssh/certificate.js.map +1 -0
- package/dist/ssh/encoding.d.ts +41 -0
- package/dist/ssh/encoding.d.ts.map +1 -0
- package/dist/ssh/encoding.js +70 -0
- package/dist/ssh/encoding.js.map +1 -0
- package/dist/ssh/keys.d.ts +92 -0
- package/dist/ssh/keys.d.ts.map +1 -0
- package/dist/ssh/keys.js +277 -0
- package/dist/ssh/keys.js.map +1 -0
- package/dist/ssh/signer.d.ts +35 -0
- package/dist/ssh/signer.d.ts.map +1 -0
- package/dist/ssh/signer.js +99 -0
- package/dist/ssh/signer.js.map +1 -0
- package/dist/types.d.ts +64 -19
- package/dist/types.d.ts.map +1 -1
- package/dist/utils.d.ts +107 -21
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +251 -65
- package/dist/utils.js.map +1 -1
- package/dist/verify.d.ts +17 -5
- package/dist/verify.d.ts.map +1 -1
- package/dist/verify.js +47 -9
- package/dist/verify.js.map +1 -1
- package/openapi.json +1942 -1
- package/package.json +4 -3
- package/src/api/operations.ts +314 -0
- package/src/api/schema.ts +146 -0
- package/src/api/v3/index.ts +21 -248
- package/src/api/v3/schema.ts +36 -128
- package/src/api/v4/index.ts +266 -0
- package/src/api/v4/schema.ts +241 -0
- package/src/certificate.ts +62 -123
- package/src/db/index.ts +196 -56
- package/src/db/migrations/0003_krl_revision.ts +15 -0
- package/src/db/migrations/index.ts +2 -0
- package/src/db/types.ts +2 -0
- package/src/index.ts +6 -4
- package/src/krl.ts +13 -197
- package/src/node/cache.ts +62 -0
- package/src/node/env.ts +6 -5
- package/src/node/index.ts +1 -0
- package/src/proof.ts +65 -31
- package/src/router.ts +67 -3
- package/src/ssh/certificate.ts +236 -0
- package/src/ssh/encoding.ts +81 -0
- package/src/ssh/keys.ts +327 -0
- package/src/ssh/signer.ts +129 -0
- package/src/types.ts +60 -20
- package/src/utils.ts +285 -81
- package/src/verify.ts +54 -14
package/README.md
CHANGED
|
@@ -41,6 +41,7 @@ Node.js 24 or later is required. Helpers for Node.js are imported from
|
|
|
41
41
|
| `fromNodeSqlite(db)` | Uses a built-in `node:sqlite` database as the `DB` binding |
|
|
42
42
|
| `secretFromFile(path)` | Reads the CA private key from a file (re-read each time it is needed) |
|
|
43
43
|
| `secretFromString(value)` | Uses a string as the CA private key |
|
|
44
|
+
| `cacheInMemory()` | Uses an in-memory cache as the optional `CACHE` binding (see [KRL Caching](#krl-caching)) |
|
|
44
45
|
| `bindingsFromEnv({ DB, PRIVATE_KEY })` | Reads and validates the remaining configuration from `process.env` |
|
|
45
46
|
|
|
46
47
|
`createSshCa(bindings)` returns a `fetch(request)` handler that takes a
|
|
@@ -216,6 +217,12 @@ sequenceDiagram
|
|
|
216
217
|
deactivate Client
|
|
217
218
|
```
|
|
218
219
|
|
|
220
|
+
Host certificate principals may be IP addresses, bare host names or fully
|
|
221
|
+
qualified domain names. As OpenSSH matches the principals of a host
|
|
222
|
+
certificate as patterns, principals containing wildcard or pattern characters
|
|
223
|
+
(`*`, `?`, `!` or `,`), whitespace or control characters are refused, as a
|
|
224
|
+
principal such as `*` would make the certificate valid for every host.
|
|
225
|
+
|
|
219
226
|
#### Renewals
|
|
220
227
|
|
|
221
228
|
Renewals are done using the existing certificate and will succeed if:
|
|
@@ -224,6 +231,12 @@ Renewals are done using the existing certificate and will succeed if:
|
|
|
224
231
|
2. It was issued by the CA
|
|
225
232
|
3. The public key used to obtain the certificate is the same as the one
|
|
226
233
|
currently presented
|
|
234
|
+
4. It has not been revoked, or already renewed
|
|
235
|
+
|
|
236
|
+
The certificate that was renewed is revoked and added to the host KRL, so it
|
|
237
|
+
cannot be used to renew again. SSH clients that check the host KRL will no
|
|
238
|
+
longer accept it, so `sshd` must be reloaded to present the renewed
|
|
239
|
+
certificate once it has been written to disk.
|
|
227
240
|
|
|
228
241
|
This means the OIDC authentication flow is not required in this case as shown
|
|
229
242
|
below when `ssh-ca-client-cli host --renew` is run:
|
|
@@ -239,12 +252,71 @@ sequenceDiagram
|
|
|
239
252
|
deactivate Client
|
|
240
253
|
```
|
|
241
254
|
|
|
255
|
+
## API
|
|
256
|
+
|
|
257
|
+
The CA serves two versions of its API, which are documented in OpenAPI
|
|
258
|
+
format at `/docs` and in [openapi.json](openapi.json):
|
|
259
|
+
|
|
260
|
+
| Action | v3 | v4 |
|
|
261
|
+
|--------------------------|---------------------------------|-------------------------------------------------|
|
|
262
|
+
| Get the CA public key | `GET /api/v3/ca` | `GET /api/v4/ca` |
|
|
263
|
+
| Request user certificate | `POST /api/v3/user/certificate` | `POST /api/v4/user/certificates` |
|
|
264
|
+
| Revoke user certificate | `POST /api/v3/user/revoke` | `DELETE /api/v4/user/certificates/{serial}` |
|
|
265
|
+
| Get user KRL | `GET /api/v3/user/krl` | `GET /api/v4/user/krl` |
|
|
266
|
+
| Request host certificate | `POST /api/v3/host/certificate` | `POST /api/v4/host/certificates` |
|
|
267
|
+
| Renew host certificate | `POST /api/v3/host/renew` | `POST /api/v4/host/certificates/{serial}/renew` |
|
|
268
|
+
| Revoke host certificate | `POST /api/v3/host/revoke` | `DELETE /api/v4/host/certificates/{serial}` |
|
|
269
|
+
| Get host KRL | `GET /api/v3/host/krl` | `GET /api/v4/host/krl` |
|
|
270
|
+
|
|
271
|
+
Certificates are issued for Ed25519, ECDSA and RSA keys. RSA keys must be at
|
|
272
|
+
least 2048 bits; a request for a smaller key is refused with a `400` error,
|
|
273
|
+
and a certificate for a smaller key cannot be renewed. Certificates for
|
|
274
|
+
smaller keys can still be revoked.
|
|
275
|
+
|
|
276
|
+
### Proof of Possession
|
|
277
|
+
|
|
278
|
+
Requests that use an SSH key prove possession of its private key by including
|
|
279
|
+
a proof of the form `${timestamp}.${fingerprint}.${signature}`, where
|
|
280
|
+
`timestamp` is the time in milliseconds since the Unix epoch, `fingerprint` is
|
|
281
|
+
the SHA256 fingerprint of the public key (for example
|
|
282
|
+
`SHA256:79mq0wtpQMTvS4+Of8VzLN0qmYWNUyTXYmqKwhEgSLs`) and `signature` is the
|
|
283
|
+
BASE64 encoded armored SSHSIG signature (as produced by `ssh-keygen -Y sign`)
|
|
284
|
+
of the signed data. The proof is rejected if the timestamp is outside of
|
|
285
|
+
`CERTIFICATE_REQUEST_TIME_SKEW_MAX` of the time on the CA.
|
|
286
|
+
|
|
287
|
+
In v3 the proof is sent as the `proof` property of the request body and the
|
|
288
|
+
signed data is `${timestamp}.${fingerprint}` using the
|
|
289
|
+
`proof-of-possession@com.github.serverless-ssh-ca.andrewheberle` namespace.
|
|
290
|
+
As this does not say which request the proof is for, a proof captured from one
|
|
291
|
+
request may be replayed against another endpoint while it is valid.
|
|
292
|
+
|
|
293
|
+
In v4 the proof is sent in the `Proof-Of-Possession` header and is bound to
|
|
294
|
+
the request it is made for. The signed data is
|
|
295
|
+
`${timestamp}.${fingerprint}.${METHOD} ${path}` using the
|
|
296
|
+
`request-proof-of-possession@com.github.serverless-ssh-ca.andrewheberle`
|
|
297
|
+
namespace, where `${METHOD} ${path}` is the request as documented in the
|
|
298
|
+
OpenAPI schema with any parameters filled in, for example:
|
|
299
|
+
|
|
300
|
+
```
|
|
301
|
+
1765696780805.SHA256:79mq0wtpQMTvS4+Of8VzLN0qmYWNUyTXYmqKwhEgSLs.DELETE /api/v4/user/certificates/12345678901234567890
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
Only `${timestamp}.${fingerprint}.${signature}` is sent, as the CA rebuilds
|
|
305
|
+
the request part from the request it receives. The path does not change if
|
|
306
|
+
the CA is served behind a proxy under another path, and serials in the path
|
|
307
|
+
are written in decimal without leading zeros.
|
|
308
|
+
|
|
309
|
+
In v4 the public key for a revocation or renewal is taken from the proof and
|
|
310
|
+
the certificate respectively, so it is not sent separately.
|
|
311
|
+
|
|
242
312
|
## Configuration
|
|
243
313
|
|
|
244
314
|
### CA Private Key
|
|
245
315
|
|
|
246
316
|
The CA private key is read from the `PRIVATE_KEY` Secrets Store binding and
|
|
247
|
-
must be an Ed25519 or ECDSA (`nistp256`, `nistp384` or
|
|
317
|
+
must be an unencrypted Ed25519 or ECDSA (`nistp256`, `nistp384` or
|
|
318
|
+
`nistp521`) key in OpenSSH format, which is what `ssh-keygen` creates by
|
|
319
|
+
default:
|
|
248
320
|
|
|
249
321
|
```sh
|
|
250
322
|
ssh-keygen -t ed25519 -N "" -C "" -f ssh-ca
|
|
@@ -274,6 +346,107 @@ Transfer the IdP settings as follows:
|
|
|
274
346
|
The `openid` and `email` scopes are required, with enabling of refresh tokens
|
|
275
347
|
(the `offline_access` scope) being optional, but recommended.
|
|
276
348
|
|
|
349
|
+
`JWT_AUD` must be set to the audience the IdP issues tokens for this
|
|
350
|
+
application with (for Cloudflare Access, the application's AUD tag). Both the
|
|
351
|
+
access token and the identity token must be issued for this audience, so
|
|
352
|
+
tokens the IdP issues for other applications are not accepted. If the IdP
|
|
353
|
+
issues access tokens with a different audience to identity tokens, set
|
|
354
|
+
`JWT_ACCESS_TOKEN_AUD` to the audience of the access token. Either value may
|
|
355
|
+
be a comma separated list. If `JWT_AUD` is not set, requests that need a
|
|
356
|
+
token fail with a `500` error.
|
|
357
|
+
|
|
358
|
+
The `email` claim of the access token must be an email address. It is used as
|
|
359
|
+
the key ID of user certificates, and with `SSH_CERTIFICATE_INCLUDE_SELF` its
|
|
360
|
+
local part (before the `@`) is added as a principal. If the IdP includes the
|
|
361
|
+
`email_verified` claim it must be `true`, so tokens for an address the IdP has
|
|
362
|
+
not verified are refused.
|
|
363
|
+
|
|
364
|
+
As the local part does not include the domain, `alice@example.com` and
|
|
365
|
+
`alice@example.net` are given the same principal with
|
|
366
|
+
`SSH_CERTIFICATE_INCLUDE_SELF`. Only enable it when the IdP only allows users
|
|
367
|
+
from domains you control.
|
|
368
|
+
|
|
369
|
+
### Rate Limiting
|
|
370
|
+
|
|
371
|
+
Requests to issue, renew and revoke certificates can be rate limited by
|
|
372
|
+
adding a [rate limit binding](https://developers.cloudflare.com/workers/runtime-apis/bindings/rate-limit/)
|
|
373
|
+
named `RATE_LIMITER` to `wrangler.jsonc`:
|
|
374
|
+
|
|
375
|
+
```jsonc
|
|
376
|
+
"ratelimits": [
|
|
377
|
+
{
|
|
378
|
+
"name": "RATE_LIMITER",
|
|
379
|
+
"namespace_id": "1001",
|
|
380
|
+
"simple": { "limit": 10, "period": 60 }
|
|
381
|
+
}
|
|
382
|
+
]
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
Requests are counted by the client IP address in the `CF-Connecting-IP`
|
|
386
|
+
header, and requests over the limit receive a `429` response. Requests to get
|
|
387
|
+
the CA public key and the KRLs are not limited, as many hosts may fetch them
|
|
388
|
+
from the same address. When no `RATE_LIMITER` binding is configured, requests
|
|
389
|
+
are not limited. If the rate limiter fails, the request is allowed and the
|
|
390
|
+
error is logged, so certificates can still be issued and revoked.
|
|
391
|
+
|
|
392
|
+
On Node.js, pass any limiter with the same shape (a `limit({ key })` method
|
|
393
|
+
returning `{ success }`, see `CaRateLimiter`) as `RATE_LIMITER`, and choose
|
|
394
|
+
the key requests are counted by with the `rateLimitKey` option, as there is no
|
|
395
|
+
`CF-Connecting-IP` header:
|
|
396
|
+
|
|
397
|
+
```ts
|
|
398
|
+
const ca = createSshCa(bindingsFromEnv({
|
|
399
|
+
DB: fromNodeSqlite(new DatabaseSync("/var/lib/ssh-ca/ssh-ca.sqlite")),
|
|
400
|
+
PRIVATE_KEY: secretFromFile("/etc/ssh-ca/ca_key"),
|
|
401
|
+
RATE_LIMITER: myLimiter,
|
|
402
|
+
}), {
|
|
403
|
+
// the client address as set by your reverse proxy
|
|
404
|
+
rateLimitKey: (req) => req.headers.get("X-Real-IP") ?? undefined,
|
|
405
|
+
})
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
Requests for which `rateLimitKey` returns `undefined` are not limited.
|
|
409
|
+
|
|
410
|
+
### KRL Caching
|
|
411
|
+
|
|
412
|
+
Generating and signing a KRL reads every revoked certificate from the
|
|
413
|
+
database, so the KRLs can be cached by adding an optional `CACHE` binding. On
|
|
414
|
+
Workers this is a KV namespace:
|
|
415
|
+
|
|
416
|
+
```jsonc
|
|
417
|
+
"kv_namespaces": [
|
|
418
|
+
{
|
|
419
|
+
"binding": "CACHE",
|
|
420
|
+
"id": "<kv namespace id>"
|
|
421
|
+
}
|
|
422
|
+
]
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
On Node.js an in-memory cache can be passed to `bindingsFromEnv`, or any
|
|
426
|
+
other cache that implements the `CaCache` interface:
|
|
427
|
+
|
|
428
|
+
```ts
|
|
429
|
+
const ca = createSshCa(bindingsFromEnv({
|
|
430
|
+
DB: fromNodeSqlite(new DatabaseSync("/var/lib/ssh-ca/ssh-ca.sqlite")),
|
|
431
|
+
PRIVATE_KEY: secretFromFile("/etc/ssh-ca/ca_key"),
|
|
432
|
+
CACHE: cacheInMemory(),
|
|
433
|
+
}))
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
A cached KRL is never served after it is out of date. Each certificate type
|
|
437
|
+
has a revision in the database that is incremented whenever a certificate of
|
|
438
|
+
that type is revoked (including the previous certificate on a host
|
|
439
|
+
renewal), or when expired certificates are removed. The cache key includes
|
|
440
|
+
this revision and the fingerprint of the CA key, so a revocation or a new CA
|
|
441
|
+
key immediately uses a new key rather than relying on cache entries being
|
|
442
|
+
deleted or the cache being consistent. Entries are left to expire after 7
|
|
443
|
+
days.
|
|
444
|
+
|
|
445
|
+
A KRL keeps the same version and generation date until its contents
|
|
446
|
+
change, so a regenerated KRL is identical to the one that was cached. If the
|
|
447
|
+
cache cannot be read or written the KRL is generated as it is without a
|
|
448
|
+
cache.
|
|
449
|
+
|
|
277
450
|
### Client
|
|
278
451
|
|
|
279
452
|
Please visit the ssh-ca-client repository for further information:
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { Logger } from "@andrewheberle/ts-slog";
|
|
2
|
+
import type { AppContext } from "../router.js";
|
|
3
|
+
import type { SshCertificate } from "../ssh/certificate.js";
|
|
4
|
+
import { type PublicKey } from "../ssh/keys.js";
|
|
5
|
+
import { CertificateType } from "../db/types.js";
|
|
6
|
+
/**
|
|
7
|
+
* Returns the public key of the CA in OpenSSH format.
|
|
8
|
+
*
|
|
9
|
+
* @param c The request context
|
|
10
|
+
* @param l The logger for the request
|
|
11
|
+
* @returns The response containing the CA public key
|
|
12
|
+
*/
|
|
13
|
+
export declare const caPublicKey: (c: AppContext, l: Logger) => Promise<Response>;
|
|
14
|
+
/**
|
|
15
|
+
* Returns the signed Key Revocation List for a type of certificate.
|
|
16
|
+
*
|
|
17
|
+
* @param c The request context
|
|
18
|
+
* @param l The logger for the request
|
|
19
|
+
* @param certificateType The type of certificate to return the KRL for
|
|
20
|
+
* @returns The response containing the KRL and its signature
|
|
21
|
+
*/
|
|
22
|
+
export declare const revocationList: (c: AppContext, l: Logger, certificateType: CertificateType) => Promise<Response>;
|
|
23
|
+
/**
|
|
24
|
+
* Signs a certificate, records it so it can later be revoked and returns it
|
|
25
|
+
* as the response to a certificate request or renewal.
|
|
26
|
+
*
|
|
27
|
+
* These are the steps shared by every API version once a request has been
|
|
28
|
+
* validated and authorised.
|
|
29
|
+
*
|
|
30
|
+
* @param c The request context
|
|
31
|
+
* @param l The logger for the request
|
|
32
|
+
* @param sign Signs and returns the certificate
|
|
33
|
+
* @param certificateType The type of certificate being issued
|
|
34
|
+
* @param renews The host certificate being renewed, which is revoked when the new certificate is recorded
|
|
35
|
+
* @returns The response containing the issued certificate
|
|
36
|
+
*/
|
|
37
|
+
export declare const issueCertificate: (c: AppContext, l: Logger, sign: () => Promise<SshCertificate>, certificateType: CertificateType, renews?: SshCertificate) => Promise<Response>;
|
|
38
|
+
/**
|
|
39
|
+
* Revokes a certificate once the requester has proven possession of the
|
|
40
|
+
* private key for the public key it was issued for.
|
|
41
|
+
*
|
|
42
|
+
* @param c The request context
|
|
43
|
+
* @param l The logger for the request
|
|
44
|
+
* @param serial The serial of the certificate to revoke
|
|
45
|
+
* @param publicKey The public key the requester has proven possession of
|
|
46
|
+
* @param certificateType The type of certificate being revoked
|
|
47
|
+
* @returns The response containing the time of revocation
|
|
48
|
+
*/
|
|
49
|
+
export declare const revoke: (c: AppContext, l: Logger, serial: bigint, publicKey: PublicKey, certificateType: CertificateType) => Promise<Response>;
|
|
50
|
+
//# sourceMappingURL=operations.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"operations.d.ts","sourceRoot":"","sources":["../../src/api/operations.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,MAAM,EAAE,MAAM,wBAAwB,CAAA;AAStD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAG9C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AAC3D,OAAO,EAAiB,KAAK,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAE9D,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAA;AAoChD;;;;;;GAMG;AACH,eAAO,MAAM,WAAW,MAAa,UAAU,KAAK,MAAM,KAAG,OAAO,CAAC,QAAQ,CAQ5E,CAAA;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc,MAAa,UAAU,KAAK,MAAM,mBAAmB,eAAe,KAAG,OAAO,CAAC,QAAQ,CA+DjH,CAAA;AAiDD;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,gBAAgB,MAAa,UAAU,KAAK,MAAM,QAAQ,MAAM,OAAO,CAAC,cAAc,CAAC,mBAAmB,eAAe,WAAW,cAAc,KAAG,OAAO,CAAC,QAAQ,CA0DjL,CAAA;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,MAAM,MAAa,UAAU,KAAK,MAAM,UAAU,MAAM,aAAa,SAAS,mBAAmB,eAAe,KAAG,OAAO,CAAC,QAAQ,CAwC/I,CAAA"}
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import { group } from "@andrewheberle/ts-slog";
|
|
2
|
+
import { ApiException, ConflictException, InputValidationException, InternalServerErrorException, NotFoundException, UnprocessableEntityException, } from "chanfana";
|
|
3
|
+
import { BadIssuerError, CertificateError } from "../certificate.js";
|
|
4
|
+
import { KeyParseError } from "../ssh/keys.js";
|
|
5
|
+
import { getPrivateKey, getPublic, UnsupportedKeyError } from "../utils.js";
|
|
6
|
+
import { KRLBuilder } from "../krl.js";
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
import { certificateTypeName } from "./schema.js";
|
|
9
|
+
import { getKrlRevision, getKrlVersion, getRevocationList, recordCertificate, recordRenewal, RevocationStatus, revocationStatus, revokeCertificate, } from "../db/index.js";
|
|
10
|
+
/**
|
|
11
|
+
* Maps an error from an operation that uses the CA key to an API error.
|
|
12
|
+
*/
|
|
13
|
+
const caKeyError = (l, err) => {
|
|
14
|
+
switch (true) {
|
|
15
|
+
case (err instanceof UnsupportedKeyError):
|
|
16
|
+
l.error(err.message);
|
|
17
|
+
return new InternalServerErrorException(err.message);
|
|
18
|
+
case (err instanceof KeyParseError):
|
|
19
|
+
l.error("error parsing private key");
|
|
20
|
+
return new InternalServerErrorException("error parsing private key");
|
|
21
|
+
case (err instanceof ApiException):
|
|
22
|
+
// re-throw any exisiting chanfana error
|
|
23
|
+
return err;
|
|
24
|
+
default:
|
|
25
|
+
// otherwise throw as InternalServerErrorException
|
|
26
|
+
l.error("unhandled error", "error", err);
|
|
27
|
+
return new InternalServerErrorException("internal server error");
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Returns the public key of the CA in OpenSSH format.
|
|
32
|
+
*
|
|
33
|
+
* @param c The request context
|
|
34
|
+
* @param l The logger for the request
|
|
35
|
+
* @returns The response containing the CA public key
|
|
36
|
+
*/
|
|
37
|
+
export const caPublicKey = async (c, l) => {
|
|
38
|
+
try {
|
|
39
|
+
const pub = await getPublic(c.env);
|
|
40
|
+
return c.text(`${pub.toString()}\n`);
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
throw caKeyError(l, err);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Returns the signed Key Revocation List for a type of certificate.
|
|
48
|
+
*
|
|
49
|
+
* @param c The request context
|
|
50
|
+
* @param l The logger for the request
|
|
51
|
+
* @param certificateType The type of certificate to return the KRL for
|
|
52
|
+
* @returns The response containing the KRL and its signature
|
|
53
|
+
*/
|
|
54
|
+
export const revocationList = async (c, l, certificateType) => {
|
|
55
|
+
try {
|
|
56
|
+
// grab private key from secret store
|
|
57
|
+
const key = await getPrivateKey(c.env);
|
|
58
|
+
// a cached KRL is keyed by the revision it was generated for, which
|
|
59
|
+
// changes whenever a certificate is revoked, and by the CA key that
|
|
60
|
+
// signed it, so a cached KRL is never used once it is out of date. The
|
|
61
|
+
// revision is read before the revoked serials, so the KRL cached for a
|
|
62
|
+
// revision includes at least every certificate revoked by then
|
|
63
|
+
const cache = c.env.CACHE;
|
|
64
|
+
const cacheKey = cache === undefined
|
|
65
|
+
? undefined
|
|
66
|
+
: `krl:${certificateTypeName(certificateType)}:${key.publicKey.fingerprint()}:${await getKrlRevision(c.env, certificateType)}`;
|
|
67
|
+
if (cache !== undefined && cacheKey !== undefined) {
|
|
68
|
+
const cached = await readCachedKrl(cache, cacheKey, l);
|
|
69
|
+
if (cached !== undefined) {
|
|
70
|
+
return c.json(cached);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// get revoked serials
|
|
74
|
+
const serials = (await getRevocationList(c.env, certificateType)).map(v => BigInt(v));
|
|
75
|
+
// generate KRL
|
|
76
|
+
const krl = new KRLBuilder(key)
|
|
77
|
+
.addSerials(serials);
|
|
78
|
+
// the version only changes when the content of the KRL does, and every
|
|
79
|
+
// KRL with the same version has the same generation date
|
|
80
|
+
const { version, generatedAt } = await getKrlVersion(c.env, certificateType, await krl.digest());
|
|
81
|
+
const krlBytes = krl.generate(version, "", generatedAt);
|
|
82
|
+
// convert to base64
|
|
83
|
+
const bytes = new Uint8Array(krlBytes);
|
|
84
|
+
let binary = "";
|
|
85
|
+
for (const byte of bytes) {
|
|
86
|
+
binary += String.fromCharCode(byte);
|
|
87
|
+
}
|
|
88
|
+
const base64Krl = btoa(binary);
|
|
89
|
+
const signature = await krl.signature();
|
|
90
|
+
const response = {
|
|
91
|
+
krl: base64Krl,
|
|
92
|
+
signature: signature,
|
|
93
|
+
};
|
|
94
|
+
if (cache !== undefined && cacheKey !== undefined) {
|
|
95
|
+
try {
|
|
96
|
+
await cache.put(cacheKey, JSON.stringify(response), { expirationTtl: KrlCacheTtl });
|
|
97
|
+
}
|
|
98
|
+
catch (err) {
|
|
99
|
+
// the KRL is still returned, it is generated again next time
|
|
100
|
+
l.warn("could not cache KRL", "error", err);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return c.json(response);
|
|
104
|
+
}
|
|
105
|
+
catch (err) {
|
|
106
|
+
throw caKeyError(l, err);
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
const KrlResponseSchema = z.object({
|
|
110
|
+
krl: z.string(),
|
|
111
|
+
signature: z.string(),
|
|
112
|
+
});
|
|
113
|
+
// how long a cached KRL is kept, in seconds. A cached KRL is only used for
|
|
114
|
+
// the revision it was generated for, so this only removes entries for earlier
|
|
115
|
+
// revisions from the cache
|
|
116
|
+
const KrlCacheTtl = 7 * 24 * 60 * 60;
|
|
117
|
+
/**
|
|
118
|
+
* Returns a cached KRL response, or undefined if there is none or it cannot be
|
|
119
|
+
* used, in which case the KRL is generated again.
|
|
120
|
+
*/
|
|
121
|
+
const readCachedKrl = async (cache, key, l) => {
|
|
122
|
+
let cached;
|
|
123
|
+
try {
|
|
124
|
+
cached = await cache.get(key);
|
|
125
|
+
}
|
|
126
|
+
catch (err) {
|
|
127
|
+
l.warn("could not read cached KRL", "error", err);
|
|
128
|
+
return undefined;
|
|
129
|
+
}
|
|
130
|
+
if (cached === null) {
|
|
131
|
+
return undefined;
|
|
132
|
+
}
|
|
133
|
+
let json;
|
|
134
|
+
try {
|
|
135
|
+
json = JSON.parse(cached);
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
l.warn("cached KRL was not valid JSON");
|
|
139
|
+
return undefined;
|
|
140
|
+
}
|
|
141
|
+
const parsed = KrlResponseSchema.safeParse(json);
|
|
142
|
+
if (!parsed.success) {
|
|
143
|
+
l.warn("cached KRL was not a valid KRL response");
|
|
144
|
+
return undefined;
|
|
145
|
+
}
|
|
146
|
+
l.debug("using cached KRL", "key", key);
|
|
147
|
+
return parsed.data;
|
|
148
|
+
};
|
|
149
|
+
/**
|
|
150
|
+
* Signs a certificate, records it so it can later be revoked and returns it
|
|
151
|
+
* as the response to a certificate request or renewal.
|
|
152
|
+
*
|
|
153
|
+
* These are the steps shared by every API version once a request has been
|
|
154
|
+
* validated and authorised.
|
|
155
|
+
*
|
|
156
|
+
* @param c The request context
|
|
157
|
+
* @param l The logger for the request
|
|
158
|
+
* @param sign Signs and returns the certificate
|
|
159
|
+
* @param certificateType The type of certificate being issued
|
|
160
|
+
* @param renews The host certificate being renewed, which is revoked when the new certificate is recorded
|
|
161
|
+
* @returns The response containing the issued certificate
|
|
162
|
+
*/
|
|
163
|
+
export const issueCertificate = async (c, l, sign, certificateType, renews) => {
|
|
164
|
+
try {
|
|
165
|
+
const certificate = await sign();
|
|
166
|
+
const response = {
|
|
167
|
+
certificate: btoa(certificate.toString())
|
|
168
|
+
};
|
|
169
|
+
let recorded;
|
|
170
|
+
try {
|
|
171
|
+
if (renews === undefined) {
|
|
172
|
+
await recordCertificate(c.env, certificate, certificate.keyId, certificateType);
|
|
173
|
+
recorded = true;
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
recorded = await recordRenewal(c.env, renews, certificate);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
catch (err) {
|
|
180
|
+
// a certificate that is not recorded cannot be revoked, so do not issue it
|
|
181
|
+
l.error("there was a problem adding issued certificate to database", "error", err);
|
|
182
|
+
throw new InternalServerErrorException("there was a problem recording the issued certificate");
|
|
183
|
+
}
|
|
184
|
+
if (!recorded) {
|
|
185
|
+
// the certificate was revoked, or renewed by another request, since it was checked
|
|
186
|
+
l.warn("certificate presented for renewal was revoked before the renewal was recorded");
|
|
187
|
+
throw new InputValidationException("the provided certificate is revoked", ["body", "certificate"]);
|
|
188
|
+
}
|
|
189
|
+
l.info("completed issuing certificate", ...group("certificate", "principals", certificate.principals.join(","), "serial", certificate.serial), ...(renews === undefined ? [] : group("renewed", "serial", renews.serial)));
|
|
190
|
+
return c.json(response);
|
|
191
|
+
}
|
|
192
|
+
catch (err) {
|
|
193
|
+
switch (true) {
|
|
194
|
+
case (err instanceof UnsupportedKeyError):
|
|
195
|
+
l.error(err.message);
|
|
196
|
+
throw new InternalServerErrorException(err.message);
|
|
197
|
+
case (err instanceof ApiException):
|
|
198
|
+
// re-throw any exisiting chanfana error
|
|
199
|
+
throw err;
|
|
200
|
+
case (err instanceof BadIssuerError):
|
|
201
|
+
throw new UnprocessableEntityException(err.message);
|
|
202
|
+
case (err instanceof CertificateError):
|
|
203
|
+
// the request was valid but asked for a certificate the CA's
|
|
204
|
+
// configuration does not allow, such as an extension that is
|
|
205
|
+
// not permitted or one without any principals
|
|
206
|
+
l.warn("certificate could not be issued", "error", err.message);
|
|
207
|
+
throw new UnprocessableEntityException(err.message);
|
|
208
|
+
default:
|
|
209
|
+
// otherwise throw as InternalServerErrorException
|
|
210
|
+
l.error("unhandled error", "error", err);
|
|
211
|
+
throw new InternalServerErrorException("internal server error");
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
/**
|
|
216
|
+
* Revokes a certificate once the requester has proven possession of the
|
|
217
|
+
* private key for the public key it was issued for.
|
|
218
|
+
*
|
|
219
|
+
* @param c The request context
|
|
220
|
+
* @param l The logger for the request
|
|
221
|
+
* @param serial The serial of the certificate to revoke
|
|
222
|
+
* @param publicKey The public key the requester has proven possession of
|
|
223
|
+
* @param certificateType The type of certificate being revoked
|
|
224
|
+
* @returns The response containing the time of revocation
|
|
225
|
+
*/
|
|
226
|
+
export const revoke = async (c, l, serial, publicKey, certificateType) => {
|
|
227
|
+
// ensure certificate can be revoked
|
|
228
|
+
const status = await revocationStatus(c.env, serial, publicKey, certificateType);
|
|
229
|
+
switch (status) {
|
|
230
|
+
case RevocationStatus.Revoked:
|
|
231
|
+
l.warn("certificate is already revoked");
|
|
232
|
+
throw new ConflictException("certificate is already revoked");
|
|
233
|
+
case RevocationStatus.NotFound:
|
|
234
|
+
l.warn("certificate not found");
|
|
235
|
+
throw new NotFoundException("certificate not found");
|
|
236
|
+
}
|
|
237
|
+
try {
|
|
238
|
+
const res = await revokeCertificate(c.env, serial);
|
|
239
|
+
if (res.length === 0) {
|
|
240
|
+
// another request revoked the certificate after its status was checked
|
|
241
|
+
l.warn("certificate was revoked by another request");
|
|
242
|
+
throw new ConflictException("certificate is already revoked");
|
|
243
|
+
}
|
|
244
|
+
if (res.length > 1) {
|
|
245
|
+
l.warn("more than one revocation result was returned", "count", res.length);
|
|
246
|
+
}
|
|
247
|
+
l.info("completed revocation");
|
|
248
|
+
return c.json({
|
|
249
|
+
revoked_at: res[0].revoked_at
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
catch (err) {
|
|
253
|
+
switch (true) {
|
|
254
|
+
case (err instanceof ApiException):
|
|
255
|
+
// re-throw any exisiting chanfana error
|
|
256
|
+
throw err;
|
|
257
|
+
default:
|
|
258
|
+
// otherwise throw as InternalServerErrorException
|
|
259
|
+
l.error("unhandled error", "error", err);
|
|
260
|
+
throw new InternalServerErrorException("internal server error");
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
//# sourceMappingURL=operations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"operations.js","sourceRoot":"","sources":["../../src/api/operations.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import z from "zod";
|
|
2
|
+
import type { SshCaBindings } from "../types.js";
|
|
3
|
+
import { CertificateType } from "../db/types.js";
|
|
4
|
+
/**
|
|
5
|
+
* Returns the name of a {@link CertificateType} as used in API paths and logs
|
|
6
|
+
* @param certificateType The type of certificate
|
|
7
|
+
* @returns "user" or "host"
|
|
8
|
+
*/
|
|
9
|
+
export declare const certificateTypeName: (certificateType: CertificateType) => "user" | "host";
|
|
10
|
+
/**
|
|
11
|
+
* Returns the OpenAPI tag that groups the endpoints for a {@link CertificateType}
|
|
12
|
+
* @param certificateType The type of certificate
|
|
13
|
+
* @returns "User" or "Host"
|
|
14
|
+
*/
|
|
15
|
+
export declare const certificateTypeTag: (certificateType: CertificateType) => "User" | "Host";
|
|
16
|
+
export declare const openapiStringByte: z.ZodPipe<z.ZodBase64, z.ZodTransform<Buffer<ArrayBuffer>, string>>;
|
|
17
|
+
export declare const proofOfPossession: z.ZodString;
|
|
18
|
+
export declare const publicKey: z.ZodPipe<z.ZodPipe<z.ZodBase64, z.ZodTransform<Buffer<ArrayBuffer>, string>>, z.ZodTransform<import("../ssh/keys.js").PublicKey, Buffer<ArrayBuffer>>>;
|
|
19
|
+
export declare const subjectPublicKey: z.ZodPipe<z.ZodPipe<z.ZodBase64, z.ZodTransform<Buffer<ArrayBuffer>, string>>, z.ZodTransform<import("../ssh/keys.js").PublicKey, Buffer<ArrayBuffer>>>;
|
|
20
|
+
export declare const createIdentityTokenSchema: (env: SshCaBindings) => z.ZodPipe<z.ZodString, z.ZodTransform<{
|
|
21
|
+
sub: string;
|
|
22
|
+
principals: string[];
|
|
23
|
+
}, string>>;
|
|
24
|
+
export declare const createAccessTokenSchema: (env: SshCaBindings) => z.ZodPipe<z.ZodString, z.ZodTransform<{
|
|
25
|
+
email: string;
|
|
26
|
+
sub: string;
|
|
27
|
+
}, string>>;
|
|
28
|
+
export declare const krl: z.ZodPipe<z.ZodBase64, z.ZodTransform<Buffer<ArrayBuffer>, string>>;
|
|
29
|
+
export declare const createHeaderSchema: (env: SshCaBindings) => z.ZodObject<{
|
|
30
|
+
Authorization: z.ZodPipe<z.ZodString, z.ZodTransform<{
|
|
31
|
+
email: string;
|
|
32
|
+
sub: string;
|
|
33
|
+
}, string>>;
|
|
34
|
+
}, z.core.$strip>;
|
|
35
|
+
export declare const certificate: z.ZodPipe<z.ZodBase64, z.ZodTransform<Buffer<ArrayBuffer>, string>>;
|
|
36
|
+
export declare const CaPublicKeyEndpointSchema: {
|
|
37
|
+
tags: string[];
|
|
38
|
+
summary: string;
|
|
39
|
+
description: string;
|
|
40
|
+
responses: {
|
|
41
|
+
"200": {
|
|
42
|
+
content: {
|
|
43
|
+
"text/plain": {
|
|
44
|
+
schema: z.ZodString;
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
description: string;
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
export declare const createRevocationListEndpointSchema: (certificateType: CertificateType) => {
|
|
52
|
+
tags: ("Host" | "User")[];
|
|
53
|
+
summary: string;
|
|
54
|
+
description: string;
|
|
55
|
+
responses: {
|
|
56
|
+
"200": {
|
|
57
|
+
content: {
|
|
58
|
+
"application/json": {
|
|
59
|
+
schema: z.ZodType<{
|
|
60
|
+
krl: Buffer<ArrayBuffer>;
|
|
61
|
+
signature: string;
|
|
62
|
+
}, unknown, z.core.$ZodTypeInternals<{
|
|
63
|
+
krl: Buffer<ArrayBuffer>;
|
|
64
|
+
signature: string;
|
|
65
|
+
}, unknown>>;
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
description: string;
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
//# sourceMappingURL=schema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/api/schema.ts"],"names":[],"mappings":"AAAA,OAAO,CAAC,MAAM,KAAK,CAAA;AACnB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAWhD,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAA;AAEhD;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,oBAAqB,eAAe,KAAG,MAAM,GAAG,MAW/E,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,oBAAqB,eAAe,KAAG,MAAM,GAAG,MAW9E,CAAA;AAED,eAAO,MAAM,iBAAiB,qEASD,CAAA;AAE7B,eAAO,MAAM,iBAAiB,aAI3B,CAAA;AAEH,eAAO,MAAM,SAAS,yJAKnB,CAAA;AAEH,eAAO,MAAM,gBAAgB,yJAM1B,CAAA;AAEH,eAAO,MAAM,yBAAyB,QAAS,aAAa;;;WAO3D,CAAA;AAED,eAAO,MAAM,uBAAuB,QAAS,aAAa;;;WAOzD,CAAA;AAED,eAAO,MAAM,GAAG,qEAC8B,CAAA;AAE9C,eAAO,MAAM,kBAAkB,QAAS,aAAa;;;;;iBAIpD,CAAA;AAED,eAAO,MAAM,WAAW,qEACqB,CAAA;AAE7C,eAAO,MAAM,yBAAyB;;;;;;YAMnC,OAAO;gBACN,YAAY;oBACX,MAAM;;;YAGR,WAAW;;;CAIb,CAAA;AAED,eAAO,MAAM,kCAAkC,oBAAqB,eAAe;;;;;;;;;;;;;;;;;;;;CAgBjF,CAAA"}
|