@andrewheberle/serverless-ssh-ca 0.21.0 → 0.22.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/openapi.json CHANGED
@@ -90,6 +90,45 @@
90
90
  "signature"
91
91
  ]
92
92
  },
93
+ "Revocation Response": {
94
+ "type": "object",
95
+ "properties": {
96
+ "revoked_at": {
97
+ "type": "string",
98
+ "format": "date-time"
99
+ }
100
+ },
101
+ "required": [
102
+ "revoked_at"
103
+ ]
104
+ },
105
+ "Certificate Revocation": {
106
+ "type": "object",
107
+ "properties": {
108
+ "serial": {
109
+ "type": "string",
110
+ "pattern": "^\\d+$",
111
+ "description": "Serial number of certificate to revoke",
112
+ "example": "12345678901234567890"
113
+ },
114
+ "public_key": {
115
+ "type": "string",
116
+ "format": "byte",
117
+ "description": "SSH public key to sign",
118
+ "example": "ZWNkc2Etc2hhMi1uaXN0cDI1NiBBQUFBRTJWalpITmhMWE5vWVRJdGJtbHpkSEF5TlRZQUFBQUlibWx6ZEhBeU5UWUFBQUJCQkdNcFNKdHRmMEl0dE5DVmMyOFR6WEZSMUQweHZPM25wNjdWemVBUXNiZFpza3JEY1lqU2x3SjZIUERtVHBYYVUwbEVhWDlMNFloYy9jQ2YxTWU5RlRrPQo="
119
+ },
120
+ "proof": {
121
+ "type": "string",
122
+ "description": "Proof of possession comprising of ${timestamp}.${fingerprint}.${signature}",
123
+ "example": "1765696780805.SHA256:79mq0wtpQMTvS4+Of8VzLN0qmYWNUyTXYmqKwhEgSLs.LS0tLS1CRUdJTiBTU0ggU0lHTkFUVVJFLS0tLS0KVTFOSVUwbEhBQUFBQVFBQUFHZ0FBQUFUWldOa2MyRXRjMmhoTWkxdWFYTjBjREkxTmdBQUFBaHVhWE4wY0RJMQpOZ0FBQUVFRUR6Ymk4OE10alBYSDFCb1JURU9HaS96aEc4QnhQNVZUUW5OQkFNMGZtMExtekErUUZkUlNVYVRFCkdNN2l0QjhINmZNZFAydlAxcElQcGUyOWVLQkJZd0FBQUFSbWFXeGxBQUFBQUFBQUFBWnphR0UxTVRJQUFBQmsKQUFBQUUyVmpaSE5oTFhOb1lUSXRibWx6ZEhBeU5UWUFBQUJKQUFBQUlHdmZVZGNDQkRXa3ZwL0ZHS3NkakgrRQpKZzZXN0EvWUJVZng2Z0srQ01raEFBQUFJUUQxOFd5cDNZejZ0eXNvdllXbEJHTXprRnlYbFRrSHM5blVVRXJqCkczNWVPQT09Ci0tLS0tRU5EIFNTSCBTSUdOQVRVUkUtLS0tLQo="
124
+ }
125
+ },
126
+ "required": [
127
+ "serial",
128
+ "public_key",
129
+ "proof"
130
+ ]
131
+ },
93
132
  "Host Certificate Request": {
94
133
  "type": "object",
95
134
  "properties": {
@@ -164,45 +203,6 @@
164
203
  "public_key",
165
204
  "proof"
166
205
  ]
167
- },
168
- "Revocation Response": {
169
- "type": "object",
170
- "properties": {
171
- "revoked_at": {
172
- "type": "string",
173
- "format": "date-time"
174
- }
175
- },
176
- "required": [
177
- "revoked_at"
178
- ]
179
- },
180
- "Certificate Revocation": {
181
- "type": "object",
182
- "properties": {
183
- "serial": {
184
- "type": "string",
185
- "pattern": "^\\d+$",
186
- "description": "Serial number of certificate to revoke",
187
- "example": "12345678901234567890"
188
- },
189
- "public_key": {
190
- "type": "string",
191
- "format": "byte",
192
- "description": "SSH public key to sign",
193
- "example": "ZWNkc2Etc2hhMi1uaXN0cDI1NiBBQUFBRTJWalpITmhMWE5vWVRJdGJtbHpkSEF5TlRZQUFBQUlibWx6ZEhBeU5UWUFBQUJCQkdNcFNKdHRmMEl0dE5DVmMyOFR6WEZSMUQweHZPM25wNjdWemVBUXNiZFpza3JEY1lqU2x3SjZIUERtVHBYYVUwbEVhWDlMNFloYy9jQ2YxTWU5RlRrPQo="
194
- },
195
- "proof": {
196
- "type": "string",
197
- "description": "Proof of possession comprising of ${timestamp}.${fingerprint}.${signature}",
198
- "example": "1765696780805.SHA256:79mq0wtpQMTvS4+Of8VzLN0qmYWNUyTXYmqKwhEgSLs.LS0tLS1CRUdJTiBTU0ggU0lHTkFUVVJFLS0tLS0KVTFOSVUwbEhBQUFBQVFBQUFHZ0FBQUFUWldOa2MyRXRjMmhoTWkxdWFYTjBjREkxTmdBQUFBaHVhWE4wY0RJMQpOZ0FBQUVFRUR6Ymk4OE10alBYSDFCb1JURU9HaS96aEc4QnhQNVZUUW5OQkFNMGZtMExtekErUUZkUlNVYVRFCkdNN2l0QjhINmZNZFAydlAxcElQcGUyOWVLQkJZd0FBQUFSbWFXeGxBQUFBQUFBQUFBWnphR0UxTVRJQUFBQmsKQUFBQUUyVmpaSE5oTFhOb1lUSXRibWx6ZEhBeU5UWUFBQUJKQUFBQUlHdmZVZGNDQkRXa3ZwL0ZHS3NkakgrRQpKZzZXN0EvWUJVZng2Z0srQ01raEFBQUFJUUQxOFd5cDNZejZ0eXNvdllXbEJHTXprRnlYbFRrSHM5blVVRXJqCkczNWVPQT09Ci0tLS0tRU5EIFNTSCBTSUdOQVRVUkUtLS0tLQo="
199
- }
200
- },
201
- "required": [
202
- "serial",
203
- "public_key",
204
- "proof"
205
- ]
206
206
  }
207
207
  },
208
208
  "parameters": {}
@@ -210,6 +210,9 @@
210
210
  "paths": {
211
211
  "/api/v3/ca": {
212
212
  "get": {
213
+ "tags": [
214
+ "CA"
215
+ ],
213
216
  "operationId": "get_ca",
214
217
  "responses": {
215
218
  "200": {
@@ -267,6 +270,9 @@
267
270
  },
268
271
  "/api/v3/user/certificate": {
269
272
  "post": {
273
+ "tags": [
274
+ "User"
275
+ ],
270
276
  "security": [
271
277
  {
272
278
  "oidcAuth": []
@@ -523,26 +529,16 @@
523
529
  }
524
530
  }
525
531
  },
526
- "/api/v3/{certificateType}/krl": {
532
+ "/api/v3/user/krl": {
527
533
  "get": {
528
- "operationId": "get_certificateType_krl",
529
- "parameters": [
530
- {
531
- "schema": {
532
- "type": "string",
533
- "enum": [
534
- "user",
535
- "host"
536
- ]
537
- },
538
- "required": true,
539
- "name": "certificateType",
540
- "in": "path"
541
- }
534
+ "tags": [
535
+ "User"
542
536
  ],
537
+ "summary": "Get the Key Revocation List for user certificates",
538
+ "operationId": "get_user_krl",
543
539
  "responses": {
544
540
  "200": {
545
- "description": "Returns an Open SSH Key Revocation List as BASE64 and an SSHSIG signature for verification",
541
+ "description": "Returns an Open SSH Key Revocation List of revoked user certificates as BASE64 and an SSHSIG signature for verification",
546
542
  "content": {
547
543
  "application/json": {
548
544
  "schema": {
@@ -594,8 +590,295 @@
594
590
  }
595
591
  }
596
592
  },
593
+ "/api/v3/user/revoke": {
594
+ "post": {
595
+ "tags": [
596
+ "User"
597
+ ],
598
+ "summary": "Revoke a user certificate",
599
+ "operationId": "post_user_revoke",
600
+ "requestBody": {
601
+ "content": {
602
+ "application/json": {
603
+ "schema": {
604
+ "$ref": "#/components/schemas/Certificate Revocation"
605
+ }
606
+ }
607
+ }
608
+ },
609
+ "responses": {
610
+ "200": {
611
+ "description": "SSH user certificate revoked successfully",
612
+ "content": {
613
+ "application/json": {
614
+ "schema": {
615
+ "$ref": "#/components/schemas/Revocation Response"
616
+ }
617
+ }
618
+ }
619
+ },
620
+ "400": {
621
+ "description": "Input Validation Error",
622
+ "content": {
623
+ "application/json": {
624
+ "schema": {
625
+ "type": "object",
626
+ "properties": {
627
+ "success": {
628
+ "type": "boolean",
629
+ "enum": [
630
+ false
631
+ ]
632
+ },
633
+ "errors": {
634
+ "type": "array",
635
+ "items": {
636
+ "type": "object",
637
+ "properties": {
638
+ "code": {
639
+ "type": "number"
640
+ },
641
+ "message": {
642
+ "type": "string"
643
+ },
644
+ "path": {
645
+ "type": "array",
646
+ "items": {
647
+ "type": "string"
648
+ }
649
+ }
650
+ },
651
+ "required": [
652
+ "code",
653
+ "message",
654
+ "path"
655
+ ]
656
+ }
657
+ }
658
+ },
659
+ "required": [
660
+ "success",
661
+ "errors"
662
+ ]
663
+ }
664
+ }
665
+ }
666
+ },
667
+ "401": {
668
+ "description": "Unauthorized",
669
+ "content": {
670
+ "application/json": {
671
+ "schema": {
672
+ "type": "object",
673
+ "properties": {
674
+ "success": {
675
+ "type": "boolean",
676
+ "enum": [
677
+ false
678
+ ]
679
+ },
680
+ "errors": {
681
+ "type": "array",
682
+ "items": {
683
+ "type": "object",
684
+ "properties": {
685
+ "code": {
686
+ "type": "number"
687
+ },
688
+ "message": {
689
+ "type": "string"
690
+ }
691
+ },
692
+ "required": [
693
+ "code",
694
+ "message"
695
+ ]
696
+ }
697
+ }
698
+ },
699
+ "required": [
700
+ "success",
701
+ "errors"
702
+ ]
703
+ }
704
+ }
705
+ }
706
+ },
707
+ "403": {
708
+ "description": "Forbidden",
709
+ "content": {
710
+ "application/json": {
711
+ "schema": {
712
+ "type": "object",
713
+ "properties": {
714
+ "success": {
715
+ "type": "boolean",
716
+ "enum": [
717
+ false
718
+ ]
719
+ },
720
+ "errors": {
721
+ "type": "array",
722
+ "items": {
723
+ "type": "object",
724
+ "properties": {
725
+ "code": {
726
+ "type": "number"
727
+ },
728
+ "message": {
729
+ "type": "string"
730
+ }
731
+ },
732
+ "required": [
733
+ "code",
734
+ "message"
735
+ ]
736
+ }
737
+ }
738
+ },
739
+ "required": [
740
+ "success",
741
+ "errors"
742
+ ]
743
+ }
744
+ }
745
+ }
746
+ },
747
+ "409": {
748
+ "description": "Conflict",
749
+ "content": {
750
+ "application/json": {
751
+ "schema": {
752
+ "type": "object",
753
+ "properties": {
754
+ "success": {
755
+ "type": "boolean",
756
+ "enum": [
757
+ false
758
+ ]
759
+ },
760
+ "errors": {
761
+ "type": "array",
762
+ "items": {
763
+ "type": "object",
764
+ "properties": {
765
+ "code": {
766
+ "type": "number"
767
+ },
768
+ "message": {
769
+ "type": "string"
770
+ }
771
+ },
772
+ "required": [
773
+ "code",
774
+ "message"
775
+ ]
776
+ }
777
+ }
778
+ },
779
+ "required": [
780
+ "success",
781
+ "errors"
782
+ ]
783
+ }
784
+ }
785
+ }
786
+ },
787
+ "422": {
788
+ "description": "Unprocessable Entity",
789
+ "content": {
790
+ "application/json": {
791
+ "schema": {
792
+ "type": "object",
793
+ "properties": {
794
+ "success": {
795
+ "type": "boolean",
796
+ "enum": [
797
+ false
798
+ ]
799
+ },
800
+ "errors": {
801
+ "type": "array",
802
+ "items": {
803
+ "type": "object",
804
+ "properties": {
805
+ "code": {
806
+ "type": "number"
807
+ },
808
+ "message": {
809
+ "type": "string"
810
+ },
811
+ "path": {
812
+ "type": "array",
813
+ "items": {
814
+ "type": "string"
815
+ }
816
+ }
817
+ },
818
+ "required": [
819
+ "code",
820
+ "message",
821
+ "path"
822
+ ]
823
+ }
824
+ }
825
+ },
826
+ "required": [
827
+ "success",
828
+ "errors"
829
+ ]
830
+ }
831
+ }
832
+ }
833
+ },
834
+ "500": {
835
+ "description": "Internal Server Error",
836
+ "content": {
837
+ "application/json": {
838
+ "schema": {
839
+ "type": "object",
840
+ "properties": {
841
+ "success": {
842
+ "type": "boolean",
843
+ "enum": [
844
+ false
845
+ ]
846
+ },
847
+ "errors": {
848
+ "type": "array",
849
+ "items": {
850
+ "type": "object",
851
+ "properties": {
852
+ "code": {
853
+ "type": "number"
854
+ },
855
+ "message": {
856
+ "type": "string"
857
+ }
858
+ },
859
+ "required": [
860
+ "code",
861
+ "message"
862
+ ]
863
+ }
864
+ }
865
+ },
866
+ "required": [
867
+ "success",
868
+ "errors"
869
+ ]
870
+ }
871
+ }
872
+ }
873
+ }
874
+ }
875
+ }
876
+ },
597
877
  "/api/v3/host/certificate": {
598
878
  "post": {
879
+ "tags": [
880
+ "Host"
881
+ ],
599
882
  "security": [
600
883
  {
601
884
  "oidcAuth": []
@@ -854,6 +1137,9 @@
854
1137
  },
855
1138
  "/api/v3/host/renew": {
856
1139
  "post": {
1140
+ "tags": [
1141
+ "Host"
1142
+ ],
857
1143
  "operationId": "post_host_renew",
858
1144
  "requestBody": {
859
1145
  "content": {
@@ -1092,23 +1378,74 @@
1092
1378
  }
1093
1379
  }
1094
1380
  },
1095
- "/api/v3/{certificateType}/revoke": {
1096
- "post": {
1097
- "operationId": "post_certificateType_revoke",
1098
- "parameters": [
1099
- {
1100
- "schema": {
1101
- "type": "string",
1102
- "enum": [
1103
- "user",
1104
- "host"
1105
- ]
1106
- },
1107
- "required": true,
1108
- "name": "certificateType",
1109
- "in": "path"
1381
+ "/api/v3/host/krl": {
1382
+ "get": {
1383
+ "tags": [
1384
+ "Host"
1385
+ ],
1386
+ "summary": "Get the Key Revocation List for host certificates",
1387
+ "operationId": "get_host_krl",
1388
+ "responses": {
1389
+ "200": {
1390
+ "description": "Returns an Open SSH Key Revocation List of revoked host certificates as BASE64 and an SSHSIG signature for verification",
1391
+ "content": {
1392
+ "application/json": {
1393
+ "schema": {
1394
+ "$ref": "#/components/schemas/Key Revocation List Response"
1395
+ }
1396
+ }
1397
+ }
1398
+ },
1399
+ "500": {
1400
+ "description": "Internal Server Error",
1401
+ "content": {
1402
+ "application/json": {
1403
+ "schema": {
1404
+ "type": "object",
1405
+ "properties": {
1406
+ "success": {
1407
+ "type": "boolean",
1408
+ "enum": [
1409
+ false
1410
+ ]
1411
+ },
1412
+ "errors": {
1413
+ "type": "array",
1414
+ "items": {
1415
+ "type": "object",
1416
+ "properties": {
1417
+ "code": {
1418
+ "type": "number"
1419
+ },
1420
+ "message": {
1421
+ "type": "string"
1422
+ }
1423
+ },
1424
+ "required": [
1425
+ "code",
1426
+ "message"
1427
+ ]
1428
+ }
1429
+ }
1430
+ },
1431
+ "required": [
1432
+ "success",
1433
+ "errors"
1434
+ ]
1435
+ }
1436
+ }
1437
+ }
1110
1438
  }
1439
+ }
1440
+ }
1441
+ },
1442
+ "/api/v3/host/revoke": {
1443
+ "post": {
1444
+ "tags": [
1445
+ "Host"
1111
1446
  ],
1447
+ "summary": "Revoke a host certificate",
1448
+ "operationId": "post_host_revoke",
1112
1449
  "requestBody": {
1113
1450
  "content": {
1114
1451
  "application/json": {
@@ -1120,7 +1457,7 @@
1120
1457
  },
1121
1458
  "responses": {
1122
1459
  "200": {
1123
- "description": "SSH certificate revoked successfully",
1460
+ "description": "SSH host certificate revoked successfully",
1124
1461
  "content": {
1125
1462
  "application/json": {
1126
1463
  "schema": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@andrewheberle/serverless-ssh-ca",
3
- "version": "0.21.0",
3
+ "version": "0.22.0",
4
4
  "description": "A serverless SSH CA built for Cloudflare Workers that can also run on Node.js",
5
5
  "type": "module",
6
6
  "types": "./dist/index.d.ts",
@@ -25,12 +25,6 @@
25
25
  "coverage": "vitest run --coverage",
26
26
  "lint": "eslint",
27
27
  "prepublishOnly": "npm run build && npm run schema",
28
- "release": "release-it",
29
- "release:ci": "npm run release:patch",
30
- "release:ci:major": "release-it major --ci",
31
- "release:ci:minor": "release-it minor --ci",
32
- "release:ci:patch": "release-it patch --ci",
33
- "release:dryrun": "release-it --dry-run",
34
28
  "schema": "node --require tsx/cjs scripts/export-schema.cjs",
35
29
  "test": "vitest run",
36
30
  "test:watch": "vitest"
@@ -65,12 +59,5 @@
65
59
  "url": "https://github.com/andrewheberle/serverless-ssh-ca/issues"
66
60
  },
67
61
  "author": "Andrew Heberle",
68
- "license": "Apache-2.0",
69
- "release-it": {
70
- "git": false,
71
- "npm": {
72
- "publish": true,
73
- "skipChecks": true
74
- }
75
- }
62
+ "license": "Apache-2.0"
76
63
  }
@@ -28,8 +28,9 @@ import {
28
28
  UnsupportedKeyError,
29
29
  } from "../../utils.js"
30
30
  import sshpk from "sshpk"
31
+ import { CertificateType } from "../../db/types.js"
31
32
  import {
32
- CertificateType,
33
+ getKrlVersion,
33
34
  getRevocationList,
34
35
  recordCertificate,
35
36
  RevocationStatus,
@@ -44,6 +45,7 @@ import {
44
45
  createHostCertificateRequestEndpointSchema,
45
46
  createHostCertificateRenewEndpointSchema,
46
47
  createRevokeCertificateEndpointSchema,
48
+ certificateTypeName,
47
49
  } from "./schema.js"
48
50
  import { logger } from "../../logger.js"
49
51
  import type { SshCaBindings } from "../../types.js"
@@ -156,22 +158,18 @@ const UserCertificateRequestEndpoint = (env: SshCaBindings) => {
156
158
  }
157
159
  }
158
160
 
159
- const RevocationListEndpoint = (env: SshCaBindings) => {
161
+ const RevocationListEndpoint = (env: SshCaBindings, certificateType: CertificateType) => {
160
162
  return class extends OpenAPIRoute {
161
- override schema = createRevocationListEndpointSchema()
163
+ override schema = createRevocationListEndpointSchema(certificateType)
162
164
 
163
165
  override async handle(c: AppContext) {
164
- const data = await this.getValidatedData<typeof this.schema>()
165
-
166
166
  const l = logger(env).with(
167
167
  ...group("request",
168
- "type", data.params.certificateType,
168
+ "type", certificateTypeName(certificateType),
169
169
  "action", "krl",
170
170
  ),
171
171
  )
172
172
 
173
- const certificateType = data.params.certificateType === "user" ? CertificateType.User : CertificateType.Host
174
-
175
173
  try {
176
174
  // get revoked serials
177
175
  const serials = (await getRevocationList(c.env, certificateType)).map(v => BigInt(v))
@@ -183,7 +181,10 @@ const RevocationListEndpoint = (env: SshCaBindings) => {
183
181
  const krl = new KRLBuilder(key)
184
182
  .addSerials(serials)
185
183
 
186
- const krlBytes = krl.generate()
184
+ // the version only changes when the content of the KRL does
185
+ const version = await getKrlVersion(c.env, certificateType, await krl.digest())
186
+
187
+ const krlBytes = krl.generate(version)
187
188
 
188
189
  // convert to base64
189
190
  const bytes = new Uint8Array(krlBytes)
@@ -367,21 +368,19 @@ const HostCertificateRenewEndpoint = (env: SshCaBindings) => {
367
368
  }
368
369
  }
369
370
 
370
- const RevokeCertificateEndpoint = (env: SshCaBindings) => {
371
+ const RevokeCertificateEndpoint = (env: SshCaBindings, certificateType: CertificateType) => {
371
372
  return class extends OpenAPIRoute {
372
- override schema = createRevokeCertificateEndpointSchema(env)
373
+ override schema = createRevokeCertificateEndpointSchema(env, certificateType)
373
374
 
374
375
  override async handle(c: AppContext) {
375
376
  const data = await this.getValidatedData<typeof this.schema>()
376
377
 
377
378
  const l = logger(env).with(
378
- ...group("request", "type", data.params.certificateType, "action", "revoke", "serial", data.body.serial),
379
+ ...group("request", "type", certificateTypeName(certificateType), "action", "revoke", "serial", data.body.serial),
379
380
  )
380
381
 
381
- const certificatetype = data.params.certificateType === "user" ? CertificateType.User : CertificateType.Host
382
-
383
382
  // ensure certificate can be revoked
384
- const status = await revocationStatus(c.env, data.body.serial, data.body.public_key.toString("ssh"), certificatetype)
383
+ const status = await revocationStatus(c.env, data.body.serial, data.body.public_key.toString("ssh"), certificateType)
385
384
  switch (status) {
386
385
  case RevocationStatus.Revoked:
387
386
  l.warn("certificate is already revoked")
@@ -430,10 +429,12 @@ export const createApi = (env: SshCaBindings) => {
430
429
 
431
430
  api.get("/ca", CaPublicKeyEndpoint(env))
432
431
  api.post("/user/certificate", UserCertificateRequestEndpoint(env))
433
- api.get("/:certificateType/krl", RevocationListEndpoint(env))
432
+ api.get("/user/krl", RevocationListEndpoint(env, CertificateType.User))
433
+ api.post("/user/revoke", RevokeCertificateEndpoint(env, CertificateType.User))
434
434
  api.post("/host/certificate", HostCertificateRequestEndpoint(env))
435
435
  api.post("/host/renew", HostCertificateRenewEndpoint(env))
436
- api.post("/:certificateType/revoke", RevokeCertificateEndpoint(env))
436
+ api.get("/host/krl", RevocationListEndpoint(env, CertificateType.Host))
437
+ api.post("/host/revoke", RevokeCertificateEndpoint(env, CertificateType.Host))
437
438
 
438
439
  return api
439
440
  }